﻿using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Folding;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Search;
using IronPython.Runtime;
using LunarSF.SHomeWorkshop.LunarMarkdownEditor.Utils;
using LunarSF.SHomeWorkshop.LunarMarkdownEditor.Utils.NPOIHelp;
using Nemmet;
using NPOI.HSSF.Record;
using NPOI.OpenXmlFormats.Vml;
using Org.BouncyCastle.Tls.Crypto;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.RightsManagement;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Threading;
using System.Xml;

namespace LunarSF.SHomeWorkshop.LunarMarkdownEditor
{
    /// <summary>
    /// 创建者：杨震宇
    /// 
    /// 说明：Markdown编辑器类。
    /// </summary>
    public class MarkdownEditorBase : TextEditor
    {
        /// <summary>
        /// 自定义高亮定义，完整定义。
        /// 但一千多行的帮助文档就明显延迟。
        /// </summary>
        private static IHighlightingDefinition advanceMarkdownHighlighting;

        /// <summary>
        /// 仅仅只提供基本的六级标题的高亮显示。
        /// </summary>
        private static IHighlightingDefinition onlyHeadersMarkdownHighlighting;

        /// <summary>
        /// 仅仅只提供列表项/注释/配置项的高亮显示，以便快速浏览任务列表。
        /// </summary>
        private static IHighlightingDefinition onlyTasksMarkdownHighlighting;

        /// <summary>
        /// 自定义的高亮显示方案（随工作区）。
        /// </summary>
        private static IHighlightingDefinition customMarkdownHighlighting = null;

        /// <summary>
        /// [静态构造方法]载入高亮定义文件。
        /// </summary>
        static MarkdownEditorBase()
        {
            // 载入代码高亮的定义文件
            //using (Stream s = typeof(MainWindow).Assembly.GetManifestResourceStream("LunarSF.SHomeWorkshop.LunarMarkdownEditor.CustomHighlighting.xshd"))

            Stream s = typeof(MainWindow).Assembly.GetManifestResourceStream("LunarSF.SHomeWorkshop.LunarMarkdownEditor.Markdown.SyntaxHightlightRulesets.xshd");
            if (s != null)
            {
                using (XmlReader reader = new XmlTextReader(s))
                {
                    advanceMarkdownHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd.
                        HighlightingLoader.Load(reader, HighlightingManager.Instance);
                }
            }

            Stream s2 = typeof(MainWindow).Assembly.GetManifestResourceStream("LunarSF.SHomeWorkshop.LunarMarkdownEditor.MarkdownHeader.SyntaxHightlightRulesets.xshd");
            if (s2 != null)
            {
                using (XmlReader reader = new XmlTextReader(s2))
                {
                    onlyHeadersMarkdownHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd.
                        HighlightingLoader.Load(reader, HighlightingManager.Instance);
                }
            }

            Stream s3 = typeof(MainWindow).Assembly.GetManifestResourceStream("LunarSF.SHomeWorkshop.LunarMarkdownEditor.MarkdownTasks.SyntaxHightlightRulesets.xshd");
            if (s3 != null)
            {
                using (XmlReader reader = new XmlTextReader(s3))
                {
                    onlyTasksMarkdownHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd.
                        HighlightingLoader.Load(reader, HighlightingManager.Instance);
                }
            }

            RloadCustomHighlightRulesets();
        }

        public static void RloadCustomHighlightRulesets()
        {
            try
            {
                var customHighlightFilePath = Globals.PathOfWorkspace + "custom_highlight_rulesets~.xshd";
                if (File.Exists(customHighlightFilePath))
                {
                    Stream s3 = new FileStream(customHighlightFilePath, FileMode.Open);
                    using (XmlReader reader = new XmlTextReader(s3))
                    {
                        customMarkdownHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd.
                            HighlightingLoader.Load(reader, HighlightingManager.Instance);
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning("自定义高亮显示方案中有错误。异常消息如下：\r\n" + ex.Message);
                customMarkdownHighlighting = null;
            }
        }

        /// <summary>
        /// 高亮显示方案。
        /// </summary>
        public enum HighlightingType
        {
            None,                              //禁用高亮
            OnlyHeaders,                       //只对六级标题高亮显示
            OnlyTasks,                         //只显示任务列表项（和分号注释/配置项） 
            Advance,                           //全部显示
            Custom,                            //自定义方案
        }

        private MarkdownEditor masterEditor;
        /// <summary>
        /// [只读]父封装。
        /// </summary>
        public MarkdownEditor MasterEditor { get { return masterEditor; } }

        public MarkdownEditor 主编辑器 { get { return masterEditor; } }

        /// <summary>
        /// [构造方法]编辑器内核。
        /// </summary>
        public MarkdownEditorBase(MarkdownEditor masterEditor)
        {
            this.masterEditor = masterEditor;

            //这个不能写死，因为会导致“字体选择”功能无效化。
            //this.FontFamily = new FontFamily("SimSun");

            this.SnapsToDevicePixels = true;//避免折叠线模糊
            this.Background = Brushes.WhiteSmoke;

            this.WordWrap = true;
            this.ShowLineNumbers = true;
            this.Options.ShowColumnRuler = true;
            this.Options.ShowSpaces = false;
            //AvalonEdit本来使用间隔号代替空格，但间隔号是全角字符，要占据两个半角的宽度；
            //我把它改为使用加重号，这样就只需要占据一个半角宽度了。
            this.Options.ShowTabs = false;
            this.Options.AutoAlignTabs = false;//将Tab显示为“--+”，并禁止Tab自动伸缩尺寸。

            //this.Options.WordWrapIndentation = Globals.MainWindow.mainTabControl.FontSize * 2;
            //直接演示Markdown文档本身时，这个效果在同段换行时非常糟糕，
            //容易出现对不齐的现象——尤其使用的字体不是宋体时更易出问题。
            //TODO： 暂时去除它。

            TextOptions.SetTextFormattingMode(this, TextFormattingMode.Display);

            this.Options.ShowBoxForControlCharacters = true;
            this.Options.ShowEndOfLine = false;
            this.Options.AllowScrollBelowDocument = true;
            this.Options.HighlightCurrentLine = true;
            this.Options.ShowColumnRuler = false;

            this.TextArea.SelectionBorder = new Pen(Brushes.Transparent, 1);
            this.TextArea.SelectionBrush = new SolidColorBrush(Color.FromArgb(255, 145, 83, 0));  // Color.FromArgb(255, 240, 106, 0) 是那两条行高亮线色
            this.TextArea.SelectionCornerRadius = 0;

            this.PreviewTextInput += MarkdownEditorBase_PreviewTextInput;
            this.TextChanged += MarkDownEditorBase_TextChanged;
            this.TextArea.SelectionChanged += TextArea_SelectionChanged;

            #region 代码高亮处理
            RefreshHighlightingSetting();
            #endregion

            this.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.DefaultIndentationStrategy();

            #region 代码折叠处理Folding            
            if (Globals.MainWindow.SupportFolding)
            {
                StartFolding();
            }
            #endregion

            #region 自动完成
            this.TextArea.TextEntered += TextArea_TextEntered;
            this.TextArea.TextEntering += TextArea_TextEntering;
            #endregion

            #region 搜索框
            if (searchPanel == null)
            {
                //searchPanel = new SearchPanel() { FontSize = Globals.MainWindow.FontSize, };
                //searchPanel.Attach(this.TextArea);
                //searchPanel = SearchPanel.Install(this.TextArea);
            }
            #endregion

            //this.TextChanged += MarkdownEditorBase_TextChanged;

            //this.Document.UndoStack.MarkAsOriginalFile();
            //加上这句后，无法撤销已保存的文件。

            this.GotFocus += MarkDownEditorBase_GotFocus;
            this.LostFocus += MarkDownEditorBase_LostFocus;
            this.PreviewKeyDown += MarkDownEditorBase_PreviewKeyDown;

            //处理拖动
            this.AllowDrop = true;

            //处理字号缩放
            this.PreviewMouseWheel += MarkDownEditorBase_PreviewMouseWheel;

            this.PreviewMouseLeftButtonUp += MarkDownEditorBase_PreviewMouseLeftButtonUp;
            this.PreviewMouseUp += MarkdownEditorBase_PreviewMouseUp;
            this.PreviewMouseDown += MarkDownEditorBase_PreviewMouseDown;
            this.PreviewMouseLeftButtonDown += MarkdownEditorBase_PreviewMouseLeftButtonDown;

            //添加快捷工具条
            popupToolBar = new LunarMarkdownEditor.EditorPopupToolBar(this) { IsOpen = false, };
            this.MouseMove += MarkDownEditorBase_MouseMove;

            this.PreviewTextInput += MarkdownEditorBase_PreviewTextInput;
        }

        /// <summary>
        /// 取指定的两个子串之间的文本。
        /// 注意：此方法不会考虑后面还有没有匹配的字串——除非继续从尾标记结束位置向后查找。
        /// </summary>
        /// <param name="header">标头</param>
        /// <param name="tail">标尾</param>
        /// <param name="startIndex">从何位置开始查找。默认值为 0。</param>
        /// <param name="byRegex">是否按正则表达式来查找，默认为 false。</param>
        /// <param name="ignoreCase">是否忽略大小写，默认值为 true。只有在 byRegex 参数为 true 时才有效。</param>
        /// <param name="multiLine">是否跨多行匹配，默认值为 true。只有在 byRegex 参数为 true 时才有意义。</param>
        /// <returns>返回标头、标尾之间的字符串，不含标头、标尾本身。</returns>
        public string 取文本片段(string header, string tail, bool byRegex = false,
             int startIndex = 0, bool ignoreCase = true, bool multiLine = true)
        {
            var result = Utils.Text.GetSpan(this.Text, header, tail, byRegex, startIndex, ignoreCase, multiLine);
            return result;
        }

        public Tuple<纯文本片段, 纯文本片段, 纯文本片段> 取文本片段信息元组(string header, string tail, bool byRegex = false,
             int startIndex = 0, bool ignoreCase = true, bool multiLine = true)
        {
            return Utils.Text.GetSpanInfo(this.Text, header, tail, byRegex, startIndex, ignoreCase, multiLine);
        }

        public Tuple<纯文本片段, 纯文本片段, 纯文本片段> 取片段信息元组(string header, string tail, bool byRegex = false,
             int startIndex = 0, bool ignoreCase = true, bool multiLine = true)
        {
            return Utils.Text.GetSpanInfo(this.Text, header, tail, byRegex, startIndex, ignoreCase, multiLine);
        }

        public Tuple<纯文本片段, 纯文本片段, 纯文本片段> 取片段信息(string header, string tail, bool byRegex = false,
             int startIndex = 0, bool ignoreCase = true, bool multiLine = true)
        {
            return Utils.Text.GetSpanInfo(this.Text, header, tail, byRegex, startIndex, ignoreCase, multiLine);
        }

        public List<Tuple<纯文本片段, 纯文本片段, 纯文本片段>> 取片段信息列表(
            IronPython.Runtime.PythonList ruleSetList, bool byRegex = false,
            int startIndex = 0, bool ignoreCase = true, bool multiLine = true)
        {
            return Utils.Text.GetSpanInfoList(this.Text, ruleSetList, byRegex, startIndex, ignoreCase, multiLine);
        }

        public List<Tuple<纯文本片段, 纯文本片段, 纯文本片段>> 取片段信息元组列表(
            IronPython.Runtime.PythonList ruleSetList, bool byRegex = false,
            int startIndex = 0, bool ignoreCase = true, bool multiLine = true)
        {
            return Utils.Text.GetSpanInfoList(this.Text, ruleSetList, byRegex, startIndex, ignoreCase, multiLine);
        }

        public List<Tuple<纯文本片段, 纯文本片段, 纯文本片段>> 取逆序片段信息列表(
            IronPython.Runtime.PythonList ruleSetList, bool byRegex = false,
            int startIndex = 0, bool ignoreCase = true, bool multiLine = true)
        {
            return Utils.Text.GetSpanInfoList(this.Text, ruleSetList, byRegex, startIndex, ignoreCase, multiLine, true);
        }

        public List<Tuple<纯文本片段, 纯文本片段, 纯文本片段>> 取逆序片段信息元组列表(
            IronPython.Runtime.PythonList ruleSetList, bool byRegex = false,
            int startIndex = 0, bool ignoreCase = true, bool multiLine = true)
        {
            return Utils.Text.GetSpanInfoList(this.Text, ruleSetList, byRegex, startIndex, ignoreCase, multiLine, true);
        }

        public List<Tuple<纯文本片段, 纯文本片段, 纯文本片段>> 取反向片段信息列表(
            IronPython.Runtime.PythonList ruleSetList, bool byRegex = false,
            int startIndex = 0, bool ignoreCase = true, bool multiLine = true)
        {
            return Utils.Text.GetSpanInfoList(this.Text, ruleSetList, byRegex, startIndex, ignoreCase, multiLine, true);
        }

        public List<Tuple<纯文本片段, 纯文本片段, 纯文本片段>> 取反向片段信息元组列表(
            IronPython.Runtime.PythonList ruleSetList, bool byRegex = false,
            int startIndex = 0, bool ignoreCase = true, bool multiLine = true)
        {
            return Utils.Text.GetSpanInfoList(this.Text, ruleSetList, byRegex, startIndex, ignoreCase, multiLine, true);
        }

        public bool 按片段信息替换(Tuple<纯文本片段, 纯文本片段, 纯文本片段> spanInfo)
        {
            if (spanInfo == null) throw new Exception("参数不能为空！");
            if (spanInfo.Item1 == null) throw new Exception("片段信息元组的标头信息不能为空！");
            if (spanInfo.Item2 == null) throw new Exception("片段信息元组的内容信息不能为空！");
            if (spanInfo.Item2 == null) throw new Exception("片段信息元组的标尾信息不能为空！");

            纯文本片段 header = spanInfo.Item1, content = spanInfo.Item2, tail = spanInfo.Item3;

            var startOffset = header.开始索引;
            var replaceLength = header.长度 + content.长度 + tail.长度;

            var newText = "";
            if (header.替换文本片段 == null)  // 不替换标头
            {
                newText += header.源文本片段;
            }
            else
            {
                newText += header.替换文本片段;
            }

            if (content.替换文本片段 == null)  // 不替换内容
            {
                newText += content.源文本片段;
            }
            else
            {
                newText += content.替换文本片段;
            }

            if (tail.替换文本片段 == null)  // 不替换标尾
            {
                newText += tail.源文本片段;
            }
            else
            {
                newText += tail.替换文本片段;
            }

            this.替换(startOffset, replaceLength, newText);

            return true;
        }

        public bool 按片段信息列表替换(IList<Tuple<纯文本片段, 纯文本片段, 纯文本片段>> spanInfoList)
        {
            // 如果直接使用 List<Tuple<纯文本片段, 纯文本片段, 纯文本片段>> 会报错。
            // 但 IronPython.Runtime.List 实现了 IList<object?> 所以用 IList<Tuple<纯文本片段, 纯文本片段, 纯文本片段>> 就没问题了。

            if (spanInfoList == null || spanInfoList.Count <= 0) throw new Exception("参数不能为 None 或空列表！");

            // var newSpansInfoList = spanInfoList;  //new List<Tuple<纯文本片段, 纯文本片段, 纯文本片段>>();

            //foreach(var spanInfo in spanInfoList)
            //{
            //    newSpansInfoList.Add(spanInfo as Tuple<纯文本片段, 纯文本片段, 纯文本片段>);      // 为啥要这样做？因为 ironPython 无法正确识别列表中嵌入三元素元组
            //}

            if (spanInfoList.Count == 1)
            {
                return 按片段信息替换(spanInfoList[0]);
            }

            // 先封装
            var newList = new List<纯文本片段>();

            for (int i = 0; i < spanInfoList.Count; i++)
            {
                var tuple = spanInfoList[i];
                if (tuple == null) throw new Exception($"第{i + 1}个元组为 None！");
                if (tuple.Item1 == null) throw new Exception($"第{i + 1}个元组的标头信息为 None！");
                if (tuple.Item2 == null) throw new Exception($"第{i + 1}个元组的内容信息为 None！");
                if (tuple.Item3 == null) throw new Exception($"第{i + 1}个元组的标尾信息为 None！");


                纯文本片段 header = tuple.Item1, content = tuple.Item2, tail = tuple.Item3;

                var startOffset = header.开始索引;
                var replaceLength = header.长度 + content.长度 + tail.长度;

                var newText = "";
                if (header.替换文本片段 == null)  // 不替换标头
                {
                    newText += header.源文本片段;
                }
                else
                {
                    newText += header.替换文本片段;
                }

                if (content.替换文本片段 == null)  // 不替换内容
                {
                    newText += content.源文本片段;
                }
                else
                {
                    newText += content.替换文本片段;
                }

                if (tail.替换文本片段 == null)  // 不替换标尾
                {
                    newText += tail.源文本片段;
                }
                else
                {
                    newText += tail.替换文本片段;
                }

                纯文本片段 newSpanInfo = new 纯文本片段()
                {
                    开始索引 = startOffset,
                    结束索引 = startOffset + replaceLength,
                    是否匹配文本 = false,
                    源文本片段 = header.源文本片段 + content.源文本片段 + tail.替换文本片段,
                    替换文本片段 = newText
                };
                newList.Add(newSpanInfo);
            }

            // 再排序
            newList.Sort((a, b) => { if (a.开始索引 < b.开始索引) return -1; else if (a.开始索引 > b.结束索引) return 1; else return 0; });

            // 数量肯定大于 1
            for (int i = 1; i < newList.Count; i++)
            {
                var preSpanInfo = newList[i - 1];
                var nextSpanInfo = newList[i];
                if (nextSpanInfo.开始索引 < preSpanInfo.结束索引)
                    throw new Exception("至少有两个指定的替换区域存在重叠，这样替换可能造成完全不能预测的结果。无法继续！");
            }

            for (int i = newList.Count - 1; i >= 0; i--)  // 逆序替换
            {
                this.替换(newList[i].开始索引, newList[i].长度, newList[i].替换文本片段);
            }

            this.Select(newList[0].开始索引, newList[0].长度);

            return true;
        }

        private string appendInfoHeader = null;
        private string appendInfoTail = null;
        private bool readAppendInfoByRegex = false;

        private Dictionary<string, string> appendInfoDict = null;

        /// <summary>
        /// 这个方法只用于按照指定的 标头、标尾 文本以及 是否按正则表达式来查找 这三个参数来获取当前文档当前状态下的附加信息。
        /// 这些附加信息应在标头文本、标尾文本之间，且单独写成一行分号注释，并以冒号来区分配置项名称和配置项的值。例如：
        /// ；[ 标头文本
        /// ；作者：老杨
        /// ；密级：公开
        /// ；] 标尾文本
        /// 这里的附加信息中有两个配置项。
        /// 此方法通常不需要直接调用。使用 取附加信息() 方法时，给 readAppendInfoFirst 参数传入 true 即可。
        /// </summary>
        public void 读取当前附加信息(string header, string tail, bool byRegex = false)
        {
            appendInfoHeader = header;
            appendInfoTail = tail;
            readAppendInfoByRegex = byRegex;

            var info = 取文本片段(header, tail, byRegex).Replace("\r\n", "\n");
            if (string.IsNullOrWhiteSpace(info)) return;

            var lines = info.Split(new char[] { '\n', }, StringSplitOptions.RemoveEmptyEntries);
            var regex = new Regex(@"^[；;].*?[：:]");
            var trimChars = new char[] { ';', '；', ':', '：', ' ', '　', '\t' };
            foreach (var line in lines)
            {
                var match = regex.Match(line);
                if (match.Success)
                {
                    var item_header = match.Value.Trim(trimChars);
                    if (string.IsNullOrWhiteSpace(item_header)) continue;

                    var item_tail = line.Substring(match.Index + match.Length);
                    if (string.IsNullOrWhiteSpace(item_tail))
                    {
                        item_tail = "";
                    }

                    if (appendInfoDict == null)
                    {
                        appendInfoDict = new Dictionary<string, string>();
                    }

                    var key = item_header.ToLower();
                    if (appendInfoDict.ContainsKey(key) == false)
                    {
                        appendInfoDict.Add(key, item_tail);  // Key 不分大小写，但 Value 分。
                    }
                    else
                    {
                        appendInfoDict[key] = item_tail;
                    }
                }
            }
        }

        public string 取附加信息(string key, bool readAppendInfoFirst = false)
        {
            if (string.IsNullOrWhiteSpace(key)) return null;

            if (appendInfoDict == null || readAppendInfoFirst)
            {
                读取当前附加信息(appendInfoHeader, appendInfoTail, readAppendInfoByRegex);
            }

            if (appendInfoDict == null || appendInfoDict.Count <= 0) return null;

            foreach (var item in appendInfoDict)
            {
                if (item.Key == key.ToLower())  // Key 总是小写
                {
                    return item.Value;
                }
            }

            return null;
        }

        /// <summary>
        /// [“取文本片段”方法的别名]取指定的两个子串之间的文本。
        /// 注意：此方法不会考虑后面还有没有匹配的字串——除非继续从尾标记结束位置向后查找。
        /// </summary>
        /// <param name="header">标头</param>
        /// <param name="tail">标尾</param>
        /// <returns>返回标头、标尾之间的字符串，不含标头、标尾本身。</returns>
        public string 取片段(string header, string tail, bool byRegex = false,
            int startIndex = 0, bool ignoreCase = true, bool multiLine = true)
        {
            return 取文本片段(header, tail, byRegex, startIndex, ignoreCase, multiLine);
        }

        private void MarkdownEditorBase_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ClickCount != 2) return;
            var curLine = this.Document.GetLineByOffset(this.SelectionStart);
            if ((this.SelectionStart != curLine.Offset && this.SelectionStart != curLine.Offset + 1) || curLine.Length <= 0) return;

            // 如果是在行首字符左（右）侧双击，自动选择相邻且相似的行。
            var fstChar = this.Document.GetText(curLine.Offset, 1);
            if (">》〉!！;； 　\t".Contains(fstChar) == false) return;       // 不是所有特殊字符都需要这样的，只这几种常会出现连续行

            // 向上倒找
            var preLineNumber = curLine.LineNumber - 1;
            var stop = false;
            while (preLineNumber >= 1 && stop == false)
            {
                var preLine = this.Document.GetLineByNumber(preLineNumber);
                if (preLine.Length <= 0) break;

                var preLineFstChar = this.Document.GetText(preLine.Offset, 1);
                switch (preLineFstChar)
                {
                    case ">":
                    case "》":
                    case "〉":
                        {
                            if (">》〉".Contains(fstChar))
                            {
                                preLineNumber -= 1;
                            }
                            break;
                        }
                    case "!":
                    case "！":
                        {
                            if ("!！".Contains(fstChar))
                            {
                                preLineNumber -= 1;
                            }
                            break;
                        }
                    case ";":
                    case "；":
                        {
                            if (";；".Contains(fstChar))
                            {
                                preLineNumber -= 1;
                            }
                            break;
                        }
                    case " ":
                    case "　":
                    case "\t":
                        {
                            if (" 　\t".Contains(fstChar))
                            {
                                preLineNumber -= 1;
                            }
                            break;
                        }
                    default:
                        {
                            stop = true;
                            break;
                        }
                }
            }

            // 向下找
            var nextLineNumber = curLine.LineNumber + 1;
            stop = false;
            while (nextLineNumber <= this.Document.LineCount && stop == false)
            {
                var nextLine = this.Document.GetLineByNumber(nextLineNumber);
                if (nextLine.Length <= 0) break;

                var nextLineFstChar = this.Document.GetText(nextLine.Offset, 1);
                switch (nextLineFstChar)
                {
                    case ">":
                    case "》":
                    case "〉":
                        {
                            if (">》〉".Contains(fstChar))
                            {
                                nextLineNumber += 1;
                            }
                            break;
                        }
                    case "!":
                    case "！":
                        {
                            if ("!！".Contains(fstChar))
                            {
                                nextLineNumber += 1;
                            }
                            break;
                        }
                    case ";":
                    case "；":
                        {
                            if (";；".Contains(fstChar))
                            {
                                nextLineNumber += 1;
                            }
                            break;
                        }
                    case " ":
                    case "　":
                    case "\t":
                        {
                            if (" 　\t".Contains(fstChar))
                            {
                                nextLineNumber += 1;
                            }
                            break;
                        }
                    default:
                        {
                            stop = true;
                            break;
                        }
                }
            }

            preLineNumber += 1;
            nextLineNumber -= 1;

            if (nextLineNumber < preLineNumber) return;

            var fstLine = this.Document.GetLineByNumber(preLineNumber);
            var lastLine = this.Document.GetLineByNumber(nextLineNumber);

            this.Select(fstLine.Offset, lastLine.Offset - fstLine.Offset + lastLine.Length);
            e.Handled = true;
        }

        private void MarkdownEditorBase_PreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            // if (e.Text != "{") return;
            //这个转换会造成一些意外的错误——例如添加一个图片链接时需要的不是【】而是[]。
            //switch (e.Text)
            //{
            //    case "[":
            //    case "［":
            //    case "〖":
            //        {
            //            this.Document.Insert(this.CaretOffset, "【");
            //            e.Handled = true;
            //            return;
            //        }
            //    case "]":
            //    case "］":
            //    case "〗":
            //        {
            //            this.Document.Insert(this.CaretOffset, "】");
            //            e.Handled = true;
            //            return;
            //        }
            //}

            if (string.IsNullOrEmpty(e.Text)) return;
            AutoCorrectInputText(e);
        }

        /// <summary>
        /// 自动更正功能。
        /// </summary>
        /// <param name="e"></param>
        private void AutoCorrectInputText(TextCompositionEventArgs e)
        {
            if (this.TextArea.Selection.IsMultiline) return;       // 不能影响纵向选取时的功能

            var selLine = this.Document.GetLineByOffset(this.SelectionStart);
            var endSelLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);

            var leftText = this.Document.GetText(selLine.Offset, this.SelectionStart - selLine.Offset);
            var rightStart = this.SelectionStart + this.SelectionLength;
            var rightText = this.Document.GetText(rightStart, endSelLine.EndOffset - rightStart);

            // 行首两个 。。 自动更正为 .. 
            if (leftText == "。" && e.Text == "。" && this.SelectedText.Length == 0)
            {
                this.Document.Replace(selLine.Offset, 1, "..");
                this.Select(selLine.Offset + 2, 0);
                e.Handled = true;
                return;
            }

            // 行首分号后面的【和】自动更正为[和]。 
            if ((leftText == "；" || leftText == ";") && e.Text.Length == 1 && "【［[".Contains(e.Text) && this.SelectedText.Length == 0)
            {
                this.Document.Replace(selLine.Offset + 1, 0, "[ ");
                this.Select(selLine.Offset + 3, 0);
                e.Handled = true;
                return;
            }

            if ((leftText == "；" || leftText == ";") && e.Text.Length == 1 && "】］]".Contains(e.Text) && this.SelectedText.Length == 0)
            {
                this.Document.Replace(selLine.Offset + 1, 0, "] ");
                this.Select(selLine.Offset + 3, 0);
                e.Handled = true;
                return;
            }

            var regCommentArea = new Regex(@"(?<=(^[；;][ 　\t]*[【［\[][ 　\t]*)).*(?=([ 　\t]*>>))");
            var matchCommentArea = regCommentArea.Match(leftText);
            if (e.Text == ">" && matchCommentArea.Success)
            {
                var commentAreaHeader = matchCommentArea.Value.Trim();
                var replacementCommentArea = $"；[ {commentAreaHeader} >>>" + "\r\n\r\n；" + "\r\n\r\n" + $"；] {commentAreaHeader}";
                this.Document.Replace(selLine.Offset, leftText.Length, replacementCommentArea);
                this.Select(selLine.Offset + commentAreaHeader.Length + 12, 0);
                e.Handled = true; return;
            }

            // 《br》 自动更正为 <br> 
            if ((leftText.EndsWith("《br", StringComparison.CurrentCultureIgnoreCase) ||
                 leftText.EndsWith("<br", StringComparison.CurrentCultureIgnoreCase) ||
                 leftText.EndsWith("〈br", StringComparison.CurrentCultureIgnoreCase))
                && (e.Text == "》" || e.Text == ">" || e.Text == "/" || e.Text == "、" || e.Text == "〉") && this.SelectedText.Length == 0)
            {
                var selOffset = this.SelectionStart;
                this.Document.Replace(selOffset - 3, 3, "<br>");
                this.Select(selOffset + 1, 0);
                e.Handled = true;
                return;
            }

            // 引用块的处理要放在书名号前面。
            if (">〉》".Contains(e.Text) && string.IsNullOrEmpty(leftText))
            {
                var selOffset = this.SelectionStart;
                var selLength = this.SelectionLength;
                this.Document.Replace(selOffset, selLength, "> ");
                this.Select(selOffset + 2, 0);
                e.Handled = true;
                return;
            }

            // 这两个要放在 <br> 的处理之后

            // 要防止书名号自动更正影响引用块输入
            if ("〉》".Contains(e.Text) && e.Text.Length == 1)
            {
                if (string.IsNullOrEmpty(leftText))
                {
                    var selOffset = this.SelectionStart;
                    var selLength = this.SelectionLength;
                    this.Document.Replace(selOffset, selLength, "> ");
                    this.Select(selOffset + 2, 0);
                    e.Handled = true;
                    return;
                }

                var regQuoter = new Regex(@"^([>》][ 　\t]{0,}){1,}$");
                var matchQuoter = regQuoter.Match(leftText);
                if (matchQuoter.Success)
                {
                    var selOffset = this.SelectionStart;
                    var selLength = this.SelectionLength;
                    this.Document.Replace(selOffset, selLength, "> ");
                    this.Select(selOffset + 2, 0);
                    e.Handled = true;
                    return;
                }
            }

            // 书名号自动更正
            if ("《〈〉》".Contains(e.Text) && e.Text.Length == 1)
            {
                if (string.IsNullOrWhiteSpace(leftText))
                {
                    if (e.Text != "《")
                    {
                        var selOffset = this.SelectionStart;
                        var selLength = this.SelectionLength;
                        this.Document.Replace(selOffset, selLength, "《");
                        this.Select(selOffset + 1, 0);
                        e.Handled = true;
                        return;
                    }
                }
                else
                {
                    var selOffset = this.SelectionStart;
                    var selLength = this.SelectionLength;

                    var lastIndexOf0 = leftText.LastIndexOf("《");
                    var lastIndexOf1 = leftText.LastIndexOf("〈");
                    var lastIndexOf2 = leftText.LastIndexOf("〉");
                    var lastIndexOf3 = leftText.LastIndexOf("》");

                    var maxIndex = new int[] { lastIndexOf0, lastIndexOf1, lastIndexOf2, lastIndexOf3 }.Max();
                    if (maxIndex < 0)
                    {
                        this.Document.Replace(selOffset, selLength, "《");
                        this.Select(selOffset + 1, 0);
                        e.Handled = true;
                        return;
                    }

                    var preChar = leftText[maxIndex];
                    switch (preChar)
                    {
                        case '《':
                            {
                                if (e.Text == "《" || e.Text == "〈")
                                {
                                    if (leftText.EndsWith("》、") || leftText.EndsWith("〉、"))
                                    {
                                        this.Document.Replace(selOffset - 1, selLength + 1, "〈");
                                        this.Select(selOffset, 0);
                                    }
                                    else
                                    {
                                        this.Document.Replace(selOffset, selLength, "〈");
                                        this.Select(selOffset + 1, 0);
                                    }
                                }
                                else  // e.Text == "》" || e.Text == "〉"
                                {
                                    if (leftText.EndsWith("》、") || leftText.EndsWith("〉、"))
                                    {
                                        this.Document.Replace(selOffset - 1, selLength + 1, "》");
                                        this.Select(selOffset, 0);
                                    }
                                    else
                                    {
                                        this.Document.Replace(selOffset, selLength, "》");
                                        this.Select(selOffset + 1, 0);
                                    }
                                }
                                e.Handled = true;
                                return;
                            }
                        case '〈':
                            {
                                if (e.Text == "《" || e.Text == "〈")
                                {
                                    if (leftText.EndsWith("》、") || leftText.EndsWith("〉、"))
                                    {
                                        this.Document.Replace(selOffset - 1, selLength + 1, "《");
                                        this.Select(selOffset, 0);
                                    }
                                    else
                                    {
                                        this.Document.Replace(selOffset, selLength, "《");
                                        this.Select(selOffset + 1, 0);
                                    }
                                }
                                else  // e.Text == "》" || e.Text == "〉"
                                {
                                    if (leftText.EndsWith("》、") || leftText.EndsWith("〉、"))
                                    {
                                        this.Document.Replace(selOffset - 1, selLength + 1, "〉");
                                        this.Select(selOffset, 0);
                                    }
                                    else
                                    {
                                        this.Document.Replace(selOffset, selLength, "〉");
                                        this.Select(selOffset + 1, 0);
                                    }
                                }
                                e.Handled = true;
                                return;
                            }
                        case '〉':
                            {
                                if (e.Text == "《" || e.Text == "〈")
                                {
                                    if (leftText.EndsWith("》、") || leftText.EndsWith("〉、"))
                                    {
                                        this.Document.Replace(selOffset - 1, selLength + 1, "〈");
                                        this.Select(selOffset, 0);
                                    }
                                    else
                                    {
                                        this.Document.Replace(selOffset, selLength, "〈");
                                        this.Select(selOffset + 1, 0);
                                    }
                                }
                                else  // e.Text == "》" || e.Text == "〉"
                                {
                                    if (leftText.EndsWith("》、") || leftText.EndsWith("〉、"))
                                    {
                                        this.Document.Replace(selOffset - 1, selLength + 1, "》");
                                        this.Select(selOffset, 0);
                                    }
                                    else
                                    {
                                        this.Document.Replace(selOffset, selLength, "》");
                                        this.Select(selOffset + 1, 0);
                                    }
                                }
                                e.Handled = true;
                                return;
                            }
                        case '》':
                            {
                                if (e.Text == "《" || e.Text == "〈")
                                {
                                    if (leftText.EndsWith("》、") || leftText.EndsWith("〉、"))
                                    {
                                        this.Document.Replace(selOffset - 1, selLength + 1, "《");
                                        this.Select(selOffset, 0);
                                    }
                                    else
                                    {
                                        this.Document.Replace(selOffset, selLength, "《");
                                        this.Select(selOffset + 1, 0);
                                    }
                                }
                                else  // e.Text == "》" || e.Text == "〉"
                                {
                                    if (leftText.EndsWith("》、") || leftText.EndsWith("〉、"))
                                    {
                                        this.Document.Replace(selOffset - 1, selLength + 1, "〉");
                                        this.Select(selOffset, 0);
                                    }
                                    else
                                    {
                                        this.Document.Replace(selOffset, selLength, "〉");
                                        this.Select(selOffset + 1, 0);
                                    }
                                }
                                e.Handled = true;
                                return;
                            }
                    }
                }
            }

            // 去除标点前“` ”中的空格。
            if ("！…（）—，。？".Contains(e.Text))
            {
                if (leftText.EndsWith("` "))
                {
                    var selLength = this.SelectionLength;
                    var selOffset = this.SelectionStart - 1;
                    this.Document.Replace(selOffset, selLength + 1, e.Text);
                    this.Select(selOffset + 1, 0);
                    e.Handled = true;
                    return;
                }
            }

            // 自动更正为单行折叠区
            // 这个有时候自作聪明，根本没有必要
            if (Globals.MainWindow.AutoCorrectSingleLineRegion)
            {
                var regWhiteHeader = new Regex(@"^[ 　]{0,3}?[\{｛]$");
                var match = regWhiteHeader.Match(LeftText + e.Text);
                if (match.Success && string.IsNullOrWhiteSpace(rightText))
                {
                    var selLength = this.SelectionLength;
                    this.Select(selLine.Offset, 0);
                    this.Document.Replace(selLine.Offset, leftText.Length + selLength, "{+ 【】  }");
                    this.Select(selLine.Offset + 4, 0);
                    e.Handled = true;
                    return;
                }
            }

            // 1、。  通常不会有这样的写法，大概率是要写成  1\. 此时可以省去切换输入法的麻烦。
            // 写 、。 时数字序号的转义写法自动转换
            var regConvertNumber = new Regex(@"(?<=\d)、。");
            var matchConvertNumber = regConvertNumber.Match(LeftText + e.Text);
            if (matchConvertNumber.Success)
            {
                this.Document.Replace(this.SelectionStart - 1, this.SelectionLength + 1, "\\.");
                this.Select(this.SelectionStart, 0);
                e.Handled = true;
                return;
            }

            if (leftText.EndsWith("=") && e.Text == "》" && leftText.Contains("《") == false)
            {
                this.Document.Replace(this.SelectionStart - 1, this.SelectionLength + 1, "=>");
                this.Select(this.SelectionStart, 0);
                e.Handled = true;
                return;
            }

            if (e.Text == "《" && rightText.StartsWith("="))
            {
                this.Document.Replace(this.SelectionStart, this.SelectionLength + 1, "<=");
                this.Select(this.SelectionStart + 2, 0);
                e.Handled = true;
                return;
            }

            if (leftText.EndsWith("《") && e.Text == "=" && rightText.StartsWith("=") == false)
            {
                this.Document.Replace(this.SelectionStart - 1, this.SelectionLength + 1, "<=");
                this.Select(this.SelectionStart, 0);
                e.Handled = true;
                return;
            }

            if (leftText == "【" && e.Text == " ")
            {
                this.Document.Replace(selLine.Offset, this.SelectionLength + 1, "[ ");
                this.Select(selLine.Offset + 2, 0);
                e.Handled = true;
                return;
            }

            if (leftText == "" && e.Text == "】")
            {
                this.Document.Replace(this.SelectionStart, this.SelectionLength, "]");
                this.Select(this.SelectionStart, 0);
                e.Handled = true;
                return;
            }

            // 行头输入问号时自动变全角。这个有点太霸道了。
            if ((string.IsNullOrEmpty(leftText) || new Regex(@"^[\?？]+$").Match(leftText).Success) && e.Text == "?")
            {
                this.Document.Replace(this.SelectionStart, this.SelectionLength, "？");
                this.Select(this.SelectionStart, 0);
                e.Handled = true;
                return;
            }

            var regQuoterHeader = new Regex(@"^[ 　]{0,3}?([>》〉][ 　\t]{0,}?){1,}$");
            var matchQuoterHeader = regQuoterHeader.Match(leftText + e.Text);
            if (matchQuoterHeader.Success /*&& string.IsNullOrWhiteSpace(rightText)*/)
            {
                if (" 　\t".Contains(e.Text))
                {
                    var selLength = this.SelectionLength;
                    this.Select(selLine.Offset, 0);

                    //var quoterMarkText = matchQuoterHeader.Value.Replace(" ", "").Replace("》", ">").Replace("〉", ">").Replace(">", "> ");
                    // 上面这个写法会导致无法输入引用块内的代码块。
                    var quoterMarkText = (matchQuoterHeader.Value).Replace("\t", " ").Replace("　", " ").Replace("》", ">").Replace("〉", ">");
                    // 如果类似“> >    ”，应保留最后的几个空格，以便输入引用块内代码块，而前面的空格应统一转换为半角空格。
                    var lastIndex = quoterMarkText.LastIndexOf(">");
                    if (lastIndex >= 0)
                    {
                        var headerLeft = quoterMarkText.Substring(0, lastIndex + 1);
                        var headerRightBlanks = quoterMarkText.Substring(lastIndex + 1);
                        quoterMarkText = headerLeft.Replace(" ", "").Replace(">", "> ").TrimEnd() + headerRightBlanks;     // 保留尾部空格！
                    }
                    this.Document.Replace(selLine.Offset, leftText.Length + selLength, quoterMarkText);
                    this.Select(selLine.Offset + quoterMarkText.Length, 0);
                    e.Handled = true;
                    return;
                }
            }

            // 这种开头两个冒号的要放在前面处理，相当于在开头写“:[]”，即既允许片段生效，但又视为普通文本段落。
            var regComment1Dbl = new Regex(@"^[ 　]{0,3}?[:：]{2}$");
            var matchComment1Dbl = regComment1Dbl.Match(leftText + e.Text);
            if (matchComment1Dbl.Success/* && string.IsNullOrWhiteSpace(rightText)*/)
            {
                var selLength = this.SelectionLength;
                this.Select(selLine.Offset, 0);
                this.Document.Replace(selLine.Offset, leftText.Length + selLength, "::");  // 两个冒号同用，占一个位置好看点。
                this.Select(selLine.Offset + 2, 0);
                e.Handled = true;
                return;
            }

            // 这个要注意双全角冒号开头的情况，所以要挪移到后面来
            if (",，。?？!！:：".Contains(e.Text) && leftText.EndsWith(e.Text))  // 注意：只有句号仅支持全角，其余四种都支持全、半角字符连写
            {
                var replacedMarksText = CustomMarkdownSupport.ReplaceMuiltyMarks(leftText);
                this.Document.Replace(selLine.Offset, leftText.Length, replacedMarksText);
                this.Select(selLine.Offset + replacedMarksText.Length, 0);
                return;
            }

            var regComment1 = new Regex(@"^[ 　]{0,3}?[:：]$");
            var matchComment1 = regComment1.Match(leftText + e.Text);
            if (matchComment1.Success /*&& string.IsNullOrWhiteSpace(rightText)*/)
            {
                var selLength = this.SelectionLength;
                this.Select(selLine.Offset, 0);
                this.Document.Replace(selLine.Offset, leftText.Length + selLength, "：");
                this.Select(selLine.Offset + 1, 0);
                e.Handled = true;
                return;
            }

            var regComment2 = new Regex(@"^[ 　]{0,3}?[;；]$");
            var matchComment2 = regComment2.Match(leftText + e.Text);
            if (matchComment2.Success /*&& string.IsNullOrWhiteSpace(rightText)*/)
            {
                var selLength = this.SelectionLength;
                this.Select(selLine.Offset, 0);
                this.Document.Replace(selLine.Offset, leftText.Length + selLength, "；");
                this.Select(selLine.Offset + 1, 0);
                e.Handled = true;
                return;
            }

            // 无序列表
            var regStar = new Regex(@"^[ 　]{0,3}?\*[ 　\t]{1,}$");
            var matchStar = regStar.Match(leftText + e.Text);
            if (matchStar.Success /*&& string.IsNullOrWhiteSpace(rightText)*/)
            {
                if (" 　\t".Contains(e.Text))
                {
                    var selLength = this.SelectionLength;
                    this.Select(selLine.Offset, 0);
                    this.Document.Replace(selLine.Offset, leftText.Length + selLength, "* ");
                    this.Select(selLine.Offset + 2, 0);
                    e.Handled = true;
                    return;
                }
            }

            // 非有序列表
            // 这个必须在有序列表前面
            var regNotOrderListItem = new Regex(@"^[ 　]{0,3}?\d{1,}?[\.。]$");  // 已输入 “1.”
            var matchNotOrderListItem = regNotOrderListItem.Match(leftText);
            if (matchNotOrderListItem.Success /*&& string.IsNullOrWhiteSpace(rightText)*/)
            {
                if (" 　\t".Contains(e.Text) == false)
                {
                    var selLength = this.SelectionLength;
                    this.Select(selLine.Offset, 0);

                    // 跟空白字符，表示格式化为有序列表
                    var notOrderListHeaderText = matchNotOrderListItem.Value.TrimEnd(new char[] { '.', '。', }) + "\\." + e.Text;
                    this.Document.Replace(selLine.Offset, leftText.Length + selLength, notOrderListHeaderText);
                    this.Select(selLine.Offset + notOrderListHeaderText.Length, 0);
                    e.Handled = true;
                    return;
                }
            }

            // 有序列表
            // 如果行首已输入的是“123.”，此时接着输入空白字符，自动格式化为列表项，否则自动将句点转义。
            var regOrderListItem = new Regex(@"^[ 　]{0,3}?\d{1,}?\\?[\.。][ 　\t]{0,}$");    // 针对： “1.” 和 “1. ”。
            var matchOrderListItem = regOrderListItem.Match(leftText);
            if (matchOrderListItem.Success /*&& string.IsNullOrWhiteSpace(rightText)*/)
            {
                if (" 　\t".Contains(e.Text))
                {
                    var selLength = this.SelectionLength;
                    this.Select(selLine.Offset, 0);

                    // 跟空白字符，表示格式化为有序列表
                    var orderListHeaderText = matchOrderListItem.Value.TrimEnd(new char[] { '\\', '.', '。', ' ', '　', '\t', }) + ". ";
                    this.Document.Replace(selLine.Offset, leftText.Length + selLength, orderListHeaderText);
                    this.Select(selLine.Offset + orderListHeaderText.Length, 0);
                    e.Handled = true;
                    return;
                }
                // else{...}  // 当“1. x”时不变，原样输出，继续完成列表项的输入。
            }

            if (e.Text == " " || e.Text == "　")
            {
                // 字母后面跟个句号，大概率是要改成 A. 这样的选择题选项啥的。
                // 如果并不是想改成这样，只需要不继续输入空格即可。
                var regLetterWithPunctuation = new Regex(@"[a-zA-Zａ-ｚＡ-Ｚ]。$");
                var matchLetterWithPunctuation = regLetterWithPunctuation.Match(leftText);
                if (matchLetterWithPunctuation.Success)
                {
                    var oldSelStart = this.SelectionStart;
                    this.Document.Replace(oldSelStart - 2, this.SelectionLength + 2,
                        ChinesePinYin.FatAlphaAndNumbersToThin(matchLetterWithPunctuation.Value.Trim(new char[] { '。', })) + ". ");
                    this.Select(oldSelStart + 1, 0);
                    e.Handled = true;
                    return;
                }
            }

            // 连续输入三个中文句号（这个改成输入省略号会很方便）
            //     1. 多标点标记在输入的是句号时，只需要两个！不会冲突。
            //     2. 半角句点连续输入三个没有必要转换成中文的省略号——保留英文连续句点更合乎习惯。
            if (e.Text == "。" && leftText.EndsWith("。。"))
            {
                var newLeftText = leftText.Trim(new char[] { '。' });
                this.Document.Replace(selLine.Offset + newLeftText.Length, leftText.Length - newLeftText.Length, "……");
                this.Select(selLine.Offset + newLeftText.Length + 2, 0);
                e.Handled = true;
                return;
            }

            // 单选按钮
            if ((e.Text == "（" || e.Text == "(") && (leftText.EndsWith("(") || leftText.EndsWith("（")))
            {
                var newLeftText = leftText.Substring(0, leftText.Length - 1);
                this.Document.Replace(selLine.Offset + newLeftText.Length, leftText.Length - newLeftText.Length, "((");
                this.Select(selLine.Offset + newLeftText.Length + 2, 0);
                e.Handled = true;
                return;
            }

            if ((e.Text == "）" || e.Text == ")") && (leftText.EndsWith(")") || leftText.EndsWith("）")))
            {
                var newLeftText = leftText.Substring(0, leftText.Length - 1);
                this.Document.Replace(selLine.Offset + newLeftText.Length, leftText.Length - newLeftText.Length, "))");
                this.Select(selLine.Offset + newLeftText.Length + 2, 0);
                e.Handled = true;
                return;
            }

            // 复选框
            if ((e.Text == "【" || e.Text == "[") && (leftText.EndsWith("[") || leftText.EndsWith("【")))
            {
                var newLeftText = leftText.Substring(0, leftText.Length - 1);
                this.Document.Replace(selLine.Offset + newLeftText.Length, leftText.Length - newLeftText.Length, "[[");
                this.Select(selLine.Offset + newLeftText.Length + 2, 0);
                e.Handled = true;
                return;
            }

            if ((e.Text == "】" || e.Text == "]") && (leftText.EndsWith("]") || leftText.EndsWith("】")))
            {
                var newLeftText = leftText.Substring(0, leftText.Length - 1);
                this.Document.Replace(selLine.Offset + newLeftText.Length, leftText.Length - newLeftText.Length, "]]");
                this.Select(selLine.Offset + newLeftText.Length + 2, 0);
                e.Handled = true;
                return;
            }

            // ＋－
            var regRadioMarkStart = new Regex(@"[\(（]{2}$");
            var matchRadioMarkStart = regRadioMarkStart.Match(leftText);
            if ("+-＋－".Contains(e.Text) && matchRadioMarkStart.Success)
            {
                string mark;
                if ("+＋".Contains(e.Text))
                {
                    mark = "+";
                }
                else
                {
                    mark = "-";
                }

                var newLeftText = leftText.Substring(0, leftText.Length - 2);
                this.Document.Replace(selLine.Offset + newLeftText.Length, leftText.Length - newLeftText.Length, $"(({mark} ");
                this.Select(selLine.Offset + newLeftText.Length + 4, 0);
                e.Handled = true;
                return;
            }

            var regCheckBoxMarkStart = new Regex(@"[\[【]{2}$");
            var matchCheckBoxMarkStart = regCheckBoxMarkStart.Match(leftText);
            if ("+-＋－".Contains(e.Text) && matchCheckBoxMarkStart.Success)
            {
                string mark;
                if ("+＋".Contains(e.Text))
                {
                    mark = "+";
                }
                else
                {
                    mark = "-";
                }

                var newLeftText = leftText.Substring(0, leftText.Length - 2);
                this.Document.Replace(selLine.Offset + newLeftText.Length, leftText.Length - newLeftText.Length, $"[[{mark} ");
                this.Select(selLine.Offset + newLeftText.Length + 4, 0);
                e.Handled = true;
                return;
            }

            // 方便输入列定义线，不需要可以按 Ctrl+Z 取消
            if (e.Text != "—" && (e.Text == "-" || e.Text == "—" || e.Text == "－"))  // -—－ 都可以，但破折号不行，因为 e.Text 获取其正确长度。
            {
                var curLineText = this.CurrentLine().Trim();
                if (curLineText.Length <= 0)
                {
                    var curLine = this.CurrentDocumentLine();
                    if (curLine.LineNumber >= 2)   // 要取前一行文本
                    {
                        var reg2DTableStart = new Regex(@"^[ 　]{0,3}[|｜]");
                        var match2DTableStart = reg2DTableStart.Match(preview_line);
                        if (match2DTableStart.Success)
                        {
                            this.Replace(curLine.Offset, 0, e.Text);
                            // 为便于撤销，此处要先完成原本应完成的操作

                            this.BeginChange();
                            this.Replace(curLine.Offset, e.Text.Length, "｜-｜\r\n｜");
                            this.Select(curLine.Offset + 6, 0);
                            curLine = this.CurrentDocumentLine();
                            if (curLine.LineNumber < this.LineCount)
                            {
                                var nextLineText = this.next_line.Trim();
                                this.Replace(curLine.EndOffset + 2, 0, "\r\n");
                            }
                            this.EndChange();
                            e.Handled = true;
                            return;
                        }
                    }
                }
            }

            // 自动输入双花括号对（{{}}）的尾标签（}}）（第二个 { 输入时自动添加 }}）。
            if (e.Text == "{" && leftText != null && leftText.EndsWith("{") &&
                (string.IsNullOrWhiteSpace(rightText) || rightText.StartsWith("}") == false))
            {
                var selOffset = this.SelectionStart;
                this.SelectedText = "{}}";
                this.Select(selOffset + 1, 0);
                e.Handled = true;
                return;
            }

            // TreeLine 一来有格式化功能，二来会与图像链接冲突，这里不应加自动格式化功能。
        }



        private void MarkdownEditorBase_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Middle)
            {
                ShowPopupToolBar(true);
            }
        }

        public bool StopFolding()
        {
            try
            {
                if (foldingUpdateTimer.IsEnabled)
                {
                    foldingUpdateTimer.Stop();
                }

                if (foldingManager != null)
                {
                    CustomFoldingManager.Uninstall(foldingManager);
                }
                foldingUpdateTimer.Tick -= foldingUpdateTimer_Tick;

                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool StartFolding()
        {
            try
            {
                if (foldingStrategy == null)
                    foldingStrategy = new CustomFoldingStrategy(this);

                foldingManager = CustomFoldingManager.Install(this.TextArea);

                foldingStrategy.UpdateFoldings(FoldingManager, this.Document);

                foldingUpdateTimer.Interval = TimeSpan.FromSeconds(0.05);//0.05=50毫秒。
                foldingUpdateTimer.Tick += foldingUpdateTimer_Tick;

                if (foldingUpdateTimer.IsEnabled == false)
                {
                    foldingUpdateTimer.Start();
                }
                return true;
            }
            catch
            {
                return false;
            }
        }

        private void MarkDownEditorBase_MouseMove(object sender, MouseEventArgs e)
        {
            if (this.popupToolBar.IsOpen == false || this.popupToolBar.Entered == false) return;

            var topLeft = this.popupToolBar.VPanel.TranslatePoint(new Point(0, 0), this);
            var bottomRight = new Point(topLeft.X + this.popupToolBar.MainBorder.ActualWidth, topLeft.Y + this.popupToolBar.MainBorder.ActualHeight);
            //var topRight = new Point(bottomRight.X, topLeft.Y);
            //var bottomLeft = new Point(topLeft.X, bottomRight.Y);

            var rect1 = new Rect(topLeft, bottomRight);
            var rect2 = new Rect(new Point(topLeft.X - 25, topLeft.Y - 25), new Point(bottomRight.X + 25, bottomRight.Y + 25));
            var rect3 = new Rect(new Point(topLeft.X - 50, topLeft.Y - 50), new Point(bottomRight.X + 50, bottomRight.Y + 50));
            var rect4 = new Rect(new Point(topLeft.X - 75, topLeft.Y - 75), new Point(bottomRight.X + 75, bottomRight.Y + 75));

            var mpt = e.GetPosition(this);

            if (rect1.Contains(mpt))
            {
                this.popupToolBar.MainBorder.Opacity = 1;
            }
            else if (rect2.Contains(mpt))
            {
                this.popupToolBar.MainBorder.Opacity = 0.75;
            }
            else if (rect3.Contains(mpt))
            {
                this.popupToolBar.MainBorder.Opacity = 0.50;
            }
            else if (rect4.Contains(mpt))
            {
                this.popupToolBar.MainBorder.Opacity = 0.25;
            }
            else
            {
                this.popupToolBar.IsOpen = false;
                this.popupToolBar.MainBorder.Opacity = 1;
                this.popupToolBar.Entered = false;
            }
            //this.popupToolBar.TbInfo.Text = topLeft.ToString() + "|" + bottomRight.ToString() + "|" + mpt.ToString();
        }

        private void MarkDownEditorBase_LostFocus(object sender, RoutedEventArgs e)
        {
            this.PopupToolBar.IsOpen = false;
        }

        private void MarkDownEditorBase_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            this.PopupToolBar.IsOpen = false;
        }

        private void MarkDownEditorBase_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            ShowPopupToolBar();
        }

        private void ShowPopupToolBar(bool? alwaysShow = false)
        {
            if (masterEditor == null || masterEditor.IsCompareAreaEditor) return;

            if (PopupToolBar.IsOpen == true)
            {
                PopupToolBar.IsOpen = false;
            }

            //if (this.SelectionLength <= 0 || Globals.MainWindow.IsPopupContextToolbarEnabled == false) return;  
            // 现在，只支持关闭内置按钮，而用户自定义脚本按钮如果存在，一定显示。2020年5月20日

            if (alwaysShow == false)
            {
                if (this.SelectionLength <= 0) return;
            }

            var line = this.Document.GetLineByOffset(this.SelectionStart);
            var lineText = this.Document.GetText(line.Offset, line.Length);
            if (lineText != null && lineText.StartsWith("　　解析＞＞"))
            {
                PopupToolBar.IsQuestionToolsBarShow = true;
            }
            else
            {
                PopupToolBar.IsQuestionToolsBarShow = false;
            }

            if (Globals.MainWindow.IsPopupContextToolbarEnabled == false)
            {
                PopupToolBar.BaseGrid.Visibility = Visibility.Collapsed;
                if (PopupToolBar.HasScriptButtonGroup == false) return;
            }
            else
            {
                PopupToolBar.BaseGrid.Visibility = Visibility.Visible;
            }

            PopupToolBar.IsOpen = true;
        }

        private EditorPopupToolBar popupToolBar;
        /// <summary>
        /// 上下文快捷工具栏。
        /// </summary>
        internal EditorPopupToolBar PopupToolBar
        {
            get
            {
                return popupToolBar;
            }
        }

        private void TextArea_SelectionChanged(object sender, EventArgs e)
        {
            if (Globals.MainWindow != null)
            {
                Globals.MainWindow.RefreshTextInfos();
            }

            try
            {
                var selText = this.TextArea.Selection.GetText().Trim();
                Regex regHtmlCharCode = new Regex(@"(?<=&#)\d{1,}?(?=;)");
                var matchHtmlCharCodes = regHtmlCharCode.Matches(selText);
                if (matchHtmlCharCodes.Count > 0 && int.TryParse(matchHtmlCharCodes[0].Value, out int code))
                {
                    if (matchHtmlCharCodes.Count > 1)
                    {
                        Globals.MainWindow.tbSwitchHtmlCharCode.Text = ((char)code).ToString() + "...";
                    }
                    else if (matchHtmlCharCodes.Count == 1)
                    {
                        Globals.MainWindow.tbSwitchHtmlCharCode.Text = ((char)code).ToString();
                    }
                    else
                    {
                        Globals.MainWindow.tbSwitchHtmlCharCode.Text = "&#";
                    }
                }
                else Globals.MainWindow.tbSwitchHtmlCharCode.Text = "&#";
            }
            catch
            {
                Globals.MainWindow.tbSwitchHtmlCharCode.Text = "&#";
            }
        }

        private void MarkDownEditorBase_TextChanged(object sender, EventArgs e)
        {
            if (Globals.MainWindow != null)
            {
                Globals.MainWindow.RefreshTextInfos();
            }
        }

        private void RefreshHighlightingSetting()
        {
            //this.SyntaxHighlighting.MainRuleSet.Rules.Clear();
            //上面这行会导致错误

            switch (highlightingSetting)
            {
                case HighlightingType.None:
                    {
                        this.SyntaxHighlighting = null;
                        break;
                    }
                case HighlightingType.OnlyHeaders:
                    {
                        HighlightingManager.Instance.RegisterHighlighting("Markdown Header Highlighting", new string[] { ".md" }, onlyHeadersMarkdownHighlighting);

                        this.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".md");
                        break;
                    }
                case HighlightingType.OnlyTasks:
                    {
                        HighlightingManager.Instance.RegisterHighlighting("Markdown Header Highlighting", new string[] { ".md" }, onlyTasksMarkdownHighlighting);

                        this.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".md");
                        break;
                    }
                case HighlightingType.Custom:
                    {
                        HighlightingManager.Instance.RegisterHighlighting("Markdown Custom Highlighting", new string[] { ".md" }, customMarkdownHighlighting);

                        this.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".md");

                        break;
                    }
                default:
                    {
                        HighlightingManager.Instance.RegisterHighlighting("Markdown Highlighting", new string[] { ".md" }, advanceMarkdownHighlighting);

                        this.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".md");
                        break;
                    }
            }

            //TODO: 载入用户自定义高亮方案。
            //this.SyntaxHighlighting.MainRuleSet.Rules.Add(new HighlightingRule() {  })
        }

        /// <summary>
        /// 按住 Ctrl 键 + 鼠标滚轮 缩放字号。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MarkDownEditorBase_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
            L.GetKeyboardState(out bool isAlt, out bool isShift, out bool isCtrl);

            if (isCtrl && !isAlt && !isShift)
            {
                if (e.Delta > 0)
                {
                    Globals.MainWindow.FontSizeUp();
                }
                else if (e.Delta < 0)
                {
                    Globals.MainWindow.FontSizeDown();
                }
                e.Handled = true;
                return;
            }

            if (isAlt && !isShift && !isCtrl)
            {
                if (e.Delta != 0)
                {
                    if (Globals.MainWindow.tcManagerPanels.SelectedItem == Globals.MainWindow.tiWorkspace)
                    {
                        HorizontalScrollTreeView(e, Globals.MainWindow.tvWorkDirectory);
                        return;
                    }
                    else if (Globals.MainWindow.tcManagerPanels.SelectedItem == Globals.MainWindow.tiPythonScripts)
                    {
                        if (Globals.MainWindow.tcPythonScripts.SelectedItem == Globals.MainWindow.tiGlobalScriptFolder)
                        {
                            HorizontalScrollTreeView(e, Globals.MainWindow.tvGlobalScriptFolder);
                            return;
                        }
                        else if (Globals.MainWindow.tcPythonScripts.SelectedItem == Globals.MainWindow.tiWorkspaceScriptFolder)
                        {
                            HorizontalScrollTreeView(e, Globals.MainWindow.tvWorkspaceScriptFolder);
                            return;
                        }
                    }
                    else if (Globals.MainWindow.tcManagerPanels.SelectedItem == Globals.MainWindow.tiRecentlyWorkspaces)
                    {
                        HorizontalScrollListBox(e, Globals.MainWindow.lbxHistoryWorkspaces);
                        return;
                    }
                    else if (Globals.MainWindow.tcManagerPanels.SelectedItem == Globals.MainWindow.tiOutLine)
                    {
                        HorizontalScrollTreeView(e, Globals.MainWindow.tvOutLine);
                        return;
                    }
                    else if (Globals.MainWindow.tcManagerPanels.SelectedItem == Globals.MainWindow.tiOutputHistory)
                    {
                        HorizontalScrollListBox(e, Globals.MainWindow.lbxHistoryOutport);
                        return;
                    }
                }
            }

            // 下面这段是没用的，要修改 AvalonEdit
            //if (isShift && !isCtrl && !isAlt && this.WordWrap == false)
            //{
            //    this.ScrollToHorizontalOffset(this.HorizontalOffset + e.Delta);
            //    e.Handled = true;
            //    return;
            //}
        }

        /// <summary>
        /// 此方法仅用于按住 Alt 流动 Markdown 编辑器时横向滚动左工具栏几个树型框以便在左工具栏较窄时察看其中的内容。
        /// </summary>
        /// <param name="e"></param>
        /// <param name="treeView"></param>
        private static void HorizontalScrollTreeView(MouseWheelEventArgs e, TreeView treeView)
        {
            // From: https://www.cnblogs.com/chuhaida/p/4019329.html
            TreeViewAutomationPeer lvap = new TreeViewAutomationPeer(treeView);
            var svap = lvap.GetPattern(PatternInterface.Scroll) as ScrollViewerAutomationPeer;
            var scroll = svap.Owner as ScrollViewer;
            scroll.ScrollToHorizontalOffset(scroll.HorizontalOffset - e.Delta);    //向右调节垂直滚动条的位置;
            e.Handled = true;
        }

        private static void HorizontalScrollListBox(MouseWheelEventArgs e, ListBox listView)
        {
            ListBoxAutomationPeer lvap = new ListBoxAutomationPeer(listView);
            var svap = lvap.GetPattern(PatternInterface.Scroll) as ScrollViewerAutomationPeer;
            var scroll = svap.Owner as ScrollViewer;
            scroll.ScrollToHorizontalOffset(scroll.HorizontalOffset - e.Delta);    //向右调节垂直滚动条的位置;
            e.Handled = true;
        }

        /// <summary>
        /// 是否支持试题编辑。需要开启“IsAutoCompletionEnabled”。
        /// </summary>
        public bool IsExamEnabled { get; set; }

        private bool isAutoCompletionEnabled = false;
        /// <summary>
        /// 是否支持自动完成。
        /// </summary>
        public bool IsAutoCompletionEnabled
        {
            get
            {
                return isAutoCompletionEnabled;
            }
            set
            {
                isAutoCompletionEnabled = value;
                if (value == false)
                {
                    if (this.completionWindow != null &&
                        this.completionWindow.Visibility == Visibility.Visible)
                    {
                        this.completionWindow.Close();
                        this.completionWindow = null;
                    }
                }
            }
        }

        /// <summary>
        /// 是否显示空格。
        /// </summary>
        public bool IsShowSpaces
        {
            get { return this.Options.ShowSpaces; }
            set { this.Options.ShowSpaces = value; }
        }

        /// <summary>
        /// 是否显示段落标记。
        /// </summary>
        public bool IsShowEndOfLine
        {
            get { return this.Options.ShowEndOfLine; }
            set { this.Options.ShowEndOfLine = value; }
        }

        /// <summary>
        /// 是否显示Tab符。
        /// </summary>
        public bool IsShowTabs
        {
            get { return this.Options.ShowTabs; }
            set { this.Options.ShowTabs = value; }
        }

        /// <summary>
        /// 是否支持英译中词典的自动提示功能。需要开启“IsAutoCompletionEnabled”。
        /// </summary>
        public bool IsEnToChineseDictEnabled { get; set; } = false;

        private MarkdownEditorBase.HighlightingType highlightingSetting = MarkdownEditorBase.HighlightingType.Advance;
        /// <summary>
        /// 采用何种高亮显示方案。
        /// </summary>
        public MarkdownEditorBase.HighlightingType HighlightingSetting
        {
            get { return highlightingSetting; }
            set
            {
                highlightingSetting = value;
                RefreshHighlightingSetting();
            }
        }

        /// <summary>
        /// [覆盖方法]只有用这个办法才能使Alt+Up/Down生效。
        /// </summary>
        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (e.KeyboardDevice.Modifiers == ModifierKeys.Alt
                && (e.KeyboardDevice.IsKeyDown(Key.RightAlt) || e.KeyboardDevice.IsKeyDown(Key.LeftAlt)))
            {
                if (e.Key == Key.Up || e.SystemKey == Key.Up)
                {
                    SwapWithPreviewLine();
                    e.Handled = true;
                    return;
                }
                else if (e.Key == Key.Down || e.SystemKey == Key.Down)
                {
                    SwapWithNextLine();
                    e.Handled = true;
                    return;
                }
                else if (e.Key == Key.Left || e.SystemKey == Key.Left)
                {
                    JoinToPreviewRealLine();  // 合并到上一实行（实行不能是分号注释）
                    e.Handled = true;
                    return;
                }
                else if (e.Key == Key.Right || e.SystemKey == Key.Right)
                {
                    JoinNextRealLine();   // 合并到上一实行（实行不能是分号注释行）
                    e.Handled = true;
                    return;
                }
                else if (e.Key == Key.OemComma || e.SystemKey == Key.OemComma ||   // 逗号，实际上是取 < 的意思
                         e.Key == Key.B || e.SystemKey == Key.B)                   // B，取 back 的意思。Alt+F 与“文件”菜单冲突，Alt+P与“偏好”菜单冲突。所以这里用“B（Back）和N（Next）”。
                {
                    // Alt+, 移动到上一“实行”行首。当一行内容过多被自动折行，而且回到行首更重要时，按光标键速度太慢。
                    // “实行”，是除了 Unicode 定义的空白字符组成的行和分号注释行之外的行。
                    MoveToPreviewRealLineHeader();
                    e.Handled = true;
                    return;
                }
                else if (e.Key == Key.OemPeriod || e.SystemKey == Key.OemPeriod ||  // 句号，实际上是取 > 的意思
                         e.Key == Key.N || e.SystemKey == Key.N)                    // N，取 next 的意思。Alt+F 与“文件”菜单冲突，Alt+P与“偏好”菜单冲突。所以这里用“B（Back）和N（Next）”。
                {
                    // Alt+. 移动到下一“实行”行首。当一行内容过多被自动折行，而且回到行首更重要时，按光标键速度太慢。
                    // “实行”，是除了 Unicode 定义的空白字符组成的行和分号注释行之外的行。
                    MoveToNextRealLineHeader();
                    e.Handled = true;
                    return;
                }

                // 下面这是用来调用脚本的，方式是 Alt+{数字键}。
                var numberKeyNames = new List<string>();
                for (int i = 0; i < 10; i++)
                {
                    numberKeyNames.Add("D" + i.ToString());
                }

                if (e.Key == Key.System && numberKeyNames.Contains(e.SystemKey.ToString()))
                {
                    // 调用特定脚本
                    Globals.MainWindow?.RunPythonScriptsByShortCuts(e.SystemKey.ToString());
                    e.Handled = true;
                }
            }

            base.OnKeyDown(e);
        }

        /// <summary>
        /// 移动到前一个“实行”行首。
        /// </summary>
        public void MoveToPreviewRealLineHeader()
        {
            try
            {
                var curLine = this.Document.GetLineByOffset(this.SelectionStart);
                var destLine = FindPreviewRealLine(curLine);
                if (destLine == null) return;
                if (destLine.LineNumber >= curLine.LineNumber) return; // 如果找到的就是当前行

                this.Select(destLine.Offset, 0);
                this.TextArea.Caret.BringCaretToView();
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning(ex.Message);
            }
        }

        /// <summary>
        /// 移动到下一个“实行”行首。
        /// </summary>
        public void MoveToNextRealLineHeader()
        {
            try
            {
                var curLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);  // 选择区尾所在行
                var destLine = FindNextRealLine(curLine);
                if (destLine == null) return;
                if (destLine.LineNumber <= curLine.LineNumber) return;   // 如果找到的就是当前行
                this.Select(destLine.Offset, 0);
                this.TextArea.Caret.BringCaretToView();
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning(ex.Message);
            }
        }

        /// <summary>
        /// 注意：如果向前找的时候碰到一个“虚行”的前一行是个分号注释，则返回这个虚行。
        /// </summary>
        private DocumentLine FindPreviewRealLine(DocumentLine curLine)
        {
            if (curLine == null) return null;
            for (int i = curLine.LineNumber - 1; i >= 1; i--)
            {
                var line = this.Document.GetLineByNumber(i);
                var lineText = this.Document.GetText(line);

                if (CustomMarkdownSupport.IsCommentLine(lineText))
                {
                    // 如果此行是注释行，则不能合并到此行。此时应返回分号注释行的下一行。
                    return this.Document.GetLineByNumber(i + 1);
                }

                if (string.IsNullOrWhiteSpace(lineText) == false)
                {
                    return line;
                }
            }
            return null;
        }

        /// <summary>
        /// 将选定文本开头所在行向上一个“实行”合并。
        /// 所谓实行，是有实际文本的行（但不包括分号注释行和其它虚行）；相对的“虚行”是指只有 Unicode 定义的空白字符的行。
        /// 执行本操作时，当前行到上一个“实行”之间的所有“虚行”都会被移除。
        /// 注意：分号注释、实行都充当“边界”，碰到分号注释，则分号注释的下一行即使是个“虚行”，也只会合并到这个虚行。
        /// </summary>
        public void JoinToPreviewRealLine()
        {
            try
            {
                var curLine = this.Document.GetLineByOffset(this.SelectionStart);
                var curLineText = this.Document.GetText(curLine.Offset, curLine.Length);
                var curLineRealText = curLineText.TrimStart();
                var curStartBlanksLength = curLineText.Length - curLineRealText.Length;

                var destLine = FindPreviewRealLine(curLine);

                var selStart = this.SelectionStart;
                var selLength = this.SelectionLength;
                int removeLength = 0;
                if (destLine == null)
                {
                    // 直接删除到文件头
                    removeLength = curLine.Offset;
                    this.Document.Remove(0, removeLength);
                    this.Select(selStart - removeLength, selLength);
                    return;
                }

                if (destLine.LineNumber >= curLine.LineNumber) return; // 如果找到的就是当前行

                // 移除 destLine.End -> curLine.Start 之间的所有字符。

                var destLineText = this.Document.GetText(destLine.Offset, destLine.Length);
                var keepASpaceChar = false;
                var regex = new Regex(@"(^[ 　]{0,3}[#＃]{1,}[ 　\t]{0,}$)|(^[ 　]{0,3}([>》\*\-\+〉][ 　\t]{0,}){1,}$)");
                if (regex.IsMatch(destLineText))
                {
                    keepASpaceChar = true;
                }
                var destLineRealText = destLineText.TrimEnd() + (keepASpaceChar ? " " : "");
                var destEndBlanksLength = destLineText.Length - destLineRealText.Length;

                removeLength = curLine.Offset - destLine.EndOffset;
                removeLength += destEndBlanksLength;
                removeLength += curStartBlanksLength;

                this.Select(destLine.Offset + destLineRealText.Length, 0);  // 在前面删除（合并到实行）是需要移动选择区的，考虑过多种方案，还是移动到上一个实行末尾好。
                this.BeginChange();
                this.Document.Remove(destLine.EndOffset - destEndBlanksLength, removeLength);   // 注意，这行要在选择操作后面
                this.TextArea.Caret.BringCaretToView();
                this.EndChange();
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning(ex.Message);
            }
        }

        /// <summary>
        /// 注意：如果向后找的时候碰到一个“虚行”的后一行是个分号注释，则返回这个虚行。
        /// </summary>
        private DocumentLine FindNextRealLine(DocumentLine curLine)
        {
            if (curLine == null) return null;
            for (int i = curLine.LineNumber + 1; i <= this.Document.LineCount; i++)
            {
                var line = this.Document.GetLineByNumber(i);
                var lineText = this.Document.GetText(line);

                if (CustomMarkdownSupport.IsCommentLine(lineText))
                {
                    // 如果此行是注释行，则不能合并到此行。此时应返回分号注释行的下一行。
                    return this.Document.GetLineByNumber(i - 1);
                }

                if (string.IsNullOrWhiteSpace(lineText) == false)
                {
                    return line;
                }
            }
            return null;
        }

        /// <summary>
        /// 将选定文本结尾所在行向下一个“实行”合并。
        /// 所谓实行，是有实际文本的行（但不包括分号注释行和其它虚行）；相对的“虚行”是指只有 Unicode 定义的空白字符的行。
        /// 执行本操作时，当前行到上一个“实行”之间的所有“虚行”都会被移除。
        /// 注意：分号注释、实行都充当“边界”，碰到分号注释，则分号注释的上一行即使是个“虚行”，也只会合并到这个虚行。
        /// </summary>
        public void JoinNextRealLine()
        {
            try
            {
                var curLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);  // 选择区尾所在行
                var curLineText = this.Document.GetText(curLine.Offset, curLine.Length);
                var curLineRealText = curLineText.TrimEnd();
                var curEndBlanksLength = curLineText.Length - curLineRealText.Length;

                var destLine = FindNextRealLine(curLine);
                var removeLength = 0;
                if (destLine == null)
                {
                    // 直接删除到末尾
                    this.Document.Remove(curLine.EndOffset, this.Document.TextLength - curLine.EndOffset);
                    return;
                }

                if (destLine.LineNumber <= curLine.LineNumber) return;   // 如果找到的就是当前行

                // 移除 curLine.End -> destLine.Start 之间的所有字符。
                var keepASpaceChar = false;
                var regex = new Regex(@"(^[ 　]{0,3}[#＃]{1,}[ 　\t]{0,}$)|(^[ 　]{0,3}([>》\*\-\+〉][ 　\t]{0,}){1,}$)");
                if (regex.IsMatch(curLineText))
                {
                    keepASpaceChar = true;
                }

                var destLineText = this.Document.GetText(destLine.Offset, destLine.Length);
                var destLineRealText = destLineText.TrimStart();
                var destStartBlanksLength = destLineText.Length - destLineRealText.Length;

                removeLength = destLine.Offset - curLine.EndOffset;
                removeLength += curEndBlanksLength;
                removeLength += destStartBlanksLength;

                var curLineEndRealIndex = curLine.EndOffset - curEndBlanksLength;

                this.BeginChange();
                this.Document.Remove(curLineEndRealIndex, removeLength);
                if (keepASpaceChar)
                {
                    this.Document.Replace(curLineEndRealIndex, 0, " ");  // 插入一个空格。
                }
                this.EndChange();
                // 向后合并到“实行”不需要移动选择区。
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning(ex.Message);
            }
        }

        /// <summary>
        /// 处理常用快捷键。
        /// </summary>
        void MarkDownEditorBase_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            this.PopupToolBar.IsOpen = false;

            bool isCtrl = false ||
                (Keyboard.GetKeyStates(Key.RightCtrl) & KeyStates.Down) > 0 ||
                (Keyboard.GetKeyStates(Key.LeftCtrl) & KeyStates.Down) > 0;

            bool isShift = false ||
                (Keyboard.GetKeyStates(Key.RightShift) & KeyStates.Down) > 0 ||
                (Keyboard.GetKeyStates(Key.LeftShift) & KeyStates.Down) > 0;

            bool isAlt = false ||
                (Keyboard.GetKeyStates(Key.RightAlt) & KeyStates.Down) > 0 ||
                (Keyboard.GetKeyStates(Key.LeftAlt) & KeyStates.Down) > 0;

            try
            {
                switch (e.Key)
                {
                    case Key.U:
                        {
                            if (isAlt && isCtrl)
                            {
                                if (isShift)
                                {
                                    // 相当于 Ctrl+Shift+Home
                                    this.Select(0, this.SelectionStart);
                                }
                                else
                                {
                                    // 相当于 Ctrl+Home
                                    this.MoveCaret(ICSharpCode.AvalonEdit.Editing.CaretMovementType.DocumentStart);
                                }
                                e.Handled = true;
                            }
                            break;
                        }
                    case Key.O:
                        {
                            if (isAlt && isCtrl)
                            {
                                if (isShift)
                                {
                                    // 相当于 Ctrl+Shift+End
                                    this.Select(this.SelectionStart + this.SelectionLength, this.Document.TextLength - this.SelectionStart - this.SelectionLength);
                                }
                                else
                                {
                                    // 相当于 Ctrl+End
                                    this.MoveCaret(ICSharpCode.AvalonEdit.Editing.CaretMovementType.DocumentEnd);
                                }
                                e.Handled = true;
                            }
                            break;
                        }
                    case Key.OemSemicolon://分号
                        {
                            int startOffset = this.SelectionStart;
                            int endOffset = this.SelectionStart + this.SelectionLength;

                            var startLine = this.Document.GetLineByOffset(startOffset);
                            var endLine = this.Document.GetLineByOffset(endOffset);

                            if (isCtrl)
                            {
                                if (isShift)
                                {
                                    //取消这些行的备注（如果行首存在分号，取消之）
                                    this.BeginChange();
                                    for (int i = endLine.LineNumber; i >= startLine.LineNumber; i--)
                                    {
                                        var line = this.Document.GetLineByNumber(i);
                                        var text = this.Document.GetText(line.Offset, 1);
                                        if (text == ";" || text == "；")
                                        {
                                            this.Document.Remove(line.Offset, 1);
                                        }
                                    }
                                    this.EndChange();
                                }
                                else
                                {
                                    //将这些行变成备注（在头部添加分号）
                                    this.BeginChange();
                                    for (int i = endLine.LineNumber; i >= startLine.LineNumber; i--)
                                    {
                                        var line = this.Document.GetLineByNumber(i);
                                        this.Document.Insert(line.Offset, "；");
                                    }
                                    this.EndChange();
                                }

                                e.Handled = true;
                            }

                            break;
                        }
                    case Key.OemOpenBrackets://左花括号
                        {
                            if (isCtrl && !isAlt)
                            {
                                e.Handled = true;
                                SwitchTaskListItemOrDateTimeLineState(false, isShift);
                            }
                            // 注意：输入 { 时自动变成 { 【】 } 不能放在这里，应该放在 TextInput 事件中。
                            break;
                        }
                    case Key.OemCloseBrackets://右花括号
                        {
                            if (isCtrl && !isAlt)
                            {
                                e.Handled = true;
                                SwitchTaskListItemOrDateTimeLineState(true, isShift);
                            }
                            break;
                        }
                    //另有用处：case Key.OemMinus:  //Markdown语法：前后各一个“*”包围的文本加粗。例如：这是一段*斜体*的文本。
                    case Key.I://为什么在这里处理？因为avalonEdit已经绑死Alt+Up，使用Alt+Up不起任何作用。
                        {
                            if (isCtrl)//移动到上一行的上方。
                            {
                                if (isShift)
                                {
                                    if (!isAlt) SwapWithPreviewLine();
                                }
                                else
                                {
                                    //斜体
                                    SwitchItalic();//Markdown语法：前后各一个“_”包围的文本加粗。例如：这是一段_斜体_的文本。
                                }
                                e.Handled = true;
                            }
                            break;
                        }
                    case Key.K://为什么用这个？因为avalonEdit已经绑死Alt+Up，使用Alt+Up不起任何作用。
                        {
                            if (isCtrl && isShift && !isAlt)//移动到下一行的下方。
                            {
                                SwapWithNextLine();

                                e.Handled = true;
                            }
                            break;
                        }
                    //另有用处：case Key.Oem8: //Markdown语法：前后两组“**”包围的文本加粗。例如：这是一段**加粗**的文本。
                    case Key.B:    //Markdown语法：前后两组“__”包围的文本加粗。例如：这是一段__加粗__的文本。
                        {
                            if (isCtrl && !isShift && !isAlt)
                            {
                                //加粗
                                SwitchBold();
                            }
                            break;
                        }
                    case Key.A:
                        {
                            if (isCtrl && !isShift && !isAlt)     // Ctrl+A，擅自更改默认行为不好，但 Ctrl+Shift+A 可能被用户当成自定义脚本快捷键了。
                            {
                                var curLineTxt = CurrentLine();
                                var curDocumentLine = this.CurrentDocumentLine();

                                if (curLineTxt != null && curLineTxt.StartsWith(".."))
                                {
                                    var preDocumentLine = curDocumentLine;
                                    while (preDocumentLine != null)
                                    {
                                        var preText = this.Document.GetText(preDocumentLine.Offset, preDocumentLine.Length);
                                        if (preText != null && preText.StartsWith(".."))
                                        {
                                            preDocumentLine = preDocumentLine.PreviousLine;
                                        }
                                        else
                                        {
                                            preDocumentLine = preDocumentLine.NextLine;
                                            break;
                                        }
                                    }

                                    var nxtDocumentLine = curDocumentLine;
                                    while (nxtDocumentLine != null)
                                    {
                                        var nxtText = this.Document.GetText(nxtDocumentLine.Offset, nxtDocumentLine.Length);
                                        if (nxtText != null && nxtText.StartsWith(".."))
                                        {
                                            nxtDocumentLine = nxtDocumentLine.NextLine;
                                        }
                                        else
                                        {
                                            nxtDocumentLine = nxtDocumentLine.PreviousLine;
                                            break;
                                        }
                                    }

                                    this.Select(preDocumentLine.Offset, nxtDocumentLine.Offset + nxtDocumentLine.Length - preDocumentLine.Offset);
                                    e.Handled = true;
                                }
                                else if (CustomMarkdownSupport.IsCommentLine(curLineTxt))
                                {
                                    var preDocumentLine = curDocumentLine;
                                    while (preDocumentLine != null)
                                    {
                                        var preText = this.Document.GetText(preDocumentLine.Offset, preDocumentLine.Length);
                                        if (CustomMarkdownSupport.IsCommentLine(preText))
                                        {
                                            preDocumentLine = preDocumentLine.PreviousLine;
                                        }
                                        else
                                        {
                                            preDocumentLine = preDocumentLine.NextLine;
                                            break;
                                        }
                                    }

                                    var nxtDocumentLine = curDocumentLine;
                                    var lstDocumentLine = curDocumentLine;
                                    while (nxtDocumentLine != null)
                                    {
                                        var nxtText = this.Document.GetText(nxtDocumentLine.Offset, nxtDocumentLine.Length);
                                        if (CustomMarkdownSupport.IsCommentLine(nxtText))
                                        {
                                            lstDocumentLine = nxtDocumentLine;
                                            nxtDocumentLine = nxtDocumentLine.NextLine;
                                        }
                                        else
                                        {
                                            nxtDocumentLine = nxtDocumentLine.PreviousLine;
                                            break;
                                        }
                                    }

                                    if (nxtDocumentLine != null)
                                    {
                                        this.Select(preDocumentLine.Offset, nxtDocumentLine.Offset + nxtDocumentLine.Length - preDocumentLine.Offset);
                                    }
                                    else
                                    {
                                        this.Select(preDocumentLine.Offset, lstDocumentLine.Offset + lstDocumentLine.Length - preDocumentLine.Offset);
                                    }
                                    e.Handled = true;
                                }
                                else if (CustomMarkdownSupport.IsTreeListTextLine(curLineTxt, true))
                                {
                                    var preDocumentLine = curDocumentLine;
                                    while (preDocumentLine != null)
                                    {
                                        var preText = this.Document.GetText(preDocumentLine.Offset, preDocumentLine.Length);
                                        if (CustomMarkdownSupport.IsTreeListTextLine(preText, true))
                                        {
                                            preDocumentLine = preDocumentLine.PreviousLine;
                                        }
                                        else
                                        {
                                            preDocumentLine = preDocumentLine.NextLine;
                                            break;
                                        }
                                    }

                                    var nxtDocumentLine = curDocumentLine;
                                    while (nxtDocumentLine != null)
                                    {
                                        var nxtText = this.Document.GetText(nxtDocumentLine.Offset, nxtDocumentLine.Length);
                                        if (CustomMarkdownSupport.IsTreeListTextLine(nxtText, true))
                                        {
                                            nxtDocumentLine = nxtDocumentLine.NextLine;
                                        }
                                        else
                                        {
                                            nxtDocumentLine = nxtDocumentLine.PreviousLine;
                                            break;
                                        }
                                    }

                                    this.Select(preDocumentLine.Offset, nxtDocumentLine.Offset + nxtDocumentLine.Length - preDocumentLine.Offset);
                                    e.Handled = true;
                                }
                                else if (CustomMarkdownSupport.IsCodeBlockLine(curLineTxt))
                                {
                                    var preDocumentLine = curDocumentLine;
                                    while (preDocumentLine != null)
                                    {
                                        var preText = this.Document.GetText(preDocumentLine.Offset, preDocumentLine.Length);
                                        if (CustomMarkdownSupport.IsCodeBlockLine(preText))
                                        {
                                            preDocumentLine = preDocumentLine.PreviousLine;
                                        }
                                        else
                                        {
                                            preDocumentLine = preDocumentLine.NextLine;
                                            break;
                                        }
                                    }

                                    var nxtDocumentLine = curDocumentLine;
                                    while (nxtDocumentLine != null)
                                    {
                                        var nxtText = this.Document.GetText(nxtDocumentLine.Offset, nxtDocumentLine.Length);
                                        if (CustomMarkdownSupport.IsCodeBlockLine(nxtText))
                                        {
                                            nxtDocumentLine = nxtDocumentLine.NextLine;
                                        }
                                        else
                                        {
                                            nxtDocumentLine = nxtDocumentLine.PreviousLine;
                                            break;
                                        }
                                    }

                                    this.Select(preDocumentLine.Offset, nxtDocumentLine.Offset + nxtDocumentLine.Length - preDocumentLine.Offset);
                                    e.Handled = true;
                                }
                                else if (CustomMarkdownSupport.IsBlockQuoteLine(curLineTxt))
                                {
                                    var preDocumentLine = curDocumentLine;
                                    while (preDocumentLine != null)
                                    {
                                        var preText = this.Document.GetText(preDocumentLine.Offset, preDocumentLine.Length);
                                        if (CustomMarkdownSupport.IsBlockQuoteLine(preText))
                                        {
                                            preDocumentLine = preDocumentLine.PreviousLine;
                                        }
                                        else
                                        {
                                            preDocumentLine = preDocumentLine.NextLine;
                                            break;
                                        }
                                    }

                                    var nxtDocumentLine = curDocumentLine;
                                    while (nxtDocumentLine != null)
                                    {
                                        var nxtText = this.Document.GetText(nxtDocumentLine.Offset, nxtDocumentLine.Length);
                                        if (CustomMarkdownSupport.IsBlockQuoteLine(nxtText))
                                        {
                                            nxtDocumentLine = nxtDocumentLine.NextLine;
                                        }
                                        else
                                        {
                                            nxtDocumentLine = nxtDocumentLine.PreviousLine;
                                            break;
                                        }
                                    }

                                    this.Select(preDocumentLine.Offset, nxtDocumentLine.Offset + nxtDocumentLine.Length - preDocumentLine.Offset);
                                    e.Handled = true;
                                }
                                else if (CustomMarkdownSupport.IsTableCaptionSplittor(curLineTxt) || CustomMarkdownSupport.IsTableRow(curLineTxt))
                                {
                                    var preDocumentLine = curDocumentLine;
                                    while (preDocumentLine != null)
                                    {
                                        var preText = this.Document.GetText(preDocumentLine.Offset, preDocumentLine.Length);
                                        if (CustomMarkdownSupport.IsTableCaptionSplittor(preText) || CustomMarkdownSupport.IsTableRow(preText))
                                        {
                                            preDocumentLine = preDocumentLine.PreviousLine;
                                        }
                                        else
                                        {
                                            preDocumentLine = preDocumentLine.NextLine;
                                            break;
                                        }
                                    }

                                    var nxtDocumentLine = curDocumentLine;
                                    while (nxtDocumentLine != null)
                                    {
                                        var nxtText = this.Document.GetText(nxtDocumentLine.Offset, nxtDocumentLine.Length);
                                        if (CustomMarkdownSupport.IsTableCaptionSplittor(nxtText) || CustomMarkdownSupport.IsTableRow(nxtText))
                                        {
                                            nxtDocumentLine = nxtDocumentLine.NextLine;
                                        }
                                        else
                                        {
                                            nxtDocumentLine = nxtDocumentLine.PreviousLine;
                                            break;
                                        }
                                    }

                                    this.Select(preDocumentLine.Offset, nxtDocumentLine.Offset + nxtDocumentLine.Length - preDocumentLine.Offset);
                                    e.Handled = true;
                                }
                            }
                            break;
                        }
                    case Key.Back://BackSpace，退格键
                        {
                            if (isShift && !isCtrl && !isAlt)
                            {
                                //删除到行首
                                var textArea = this.TextArea;
                                if (textArea != null && textArea.IsKeyboardFocused)
                                {
                                    var curLine = this.Document.GetLineByOffset(this.SelectionStart);
                                    var curLineText = this.Document.GetText(curLine.Offset, curLine.Length);

                                    var regExam = new Regex(@"^[　]{1,}((试题)|(解析)|(错项)|(答案)|(材料)|(出处)|(问题))＞{2,2}(?=.{1,})");
                                    var matchExam = regExam.Match(curLineText);
                                    if (matchExam.Success)
                                    {
                                        var offset = curLine.Offset + matchExam.Length;
                                        var endSelOffset = this.SelectionStart + this.SelectionLength;
                                        if (endSelOffset > offset)
                                        {
                                            this.Select(offset, endSelOffset - offset);
                                        }
                                        else
                                        {
                                            this.Select(curLine.Offset, matchExam.Length);
                                        }
                                        textArea.PerformTextInput("");
                                    }
                                    else
                                    {
                                        var fstSelectedLine = this.Document.GetLineByOffset(this.SelectionStart);

                                        this.Select(fstSelectedLine.Offset, textArea.Selection.Length + (this.SelectionStart - fstSelectedLine.Offset));
                                        textArea.PerformTextInput("");

                                        if (CustomMarkdownSupport.IsTreeListTextLine(curLineText))
                                        {
                                            FormatTextTable();
                                        }
                                    }
                                    e.Handled = true;
                                }
                            }
                            break;
                        }
                    case Key.M:
                        {
                            if (isCtrl && !isAlt)  // 为防止与某些输入法的快捷键冲突，此处可以使用 Shift 也可以不使用。
                            {
                                #region 处理 emmet
                                var selEndOffset = SelectionStart + SelectionLength;
                                var line = Document.GetLineByOffset(selEndOffset);
                                var lineText = Document.GetText(line.Offset, selEndOffset - line.Offset);

                                if (string.IsNullOrWhiteSpace(lineText) == false && IsEmmetCode(lineText))
                                {
                                    var html = FormatHtmlSpan(NemmetParser.GetHtml(lineText.TrimStart(new char[] { ' ', '　', })));
                                    if (string.IsNullOrWhiteSpace(html) == false)
                                    {
                                        html = Regex.Replace(html, @">[ \t]{1,}<", "><");
                                        this.Document.Replace(line.Offset, selEndOffset - line.Offset, html);
                                        var fstEndTagIndex = html.IndexOf("</");
                                        if (fstEndTagIndex > 0)
                                        {
                                            var preText = html.Substring(0, fstEndTagIndex);
                                            if (preText.EndsWith(">"))
                                            {
                                                this.Select(line.Offset + fstEndTagIndex, 0);
                                            }
                                            else if (preText.EndsWith("\r\n"))
                                            {
                                                this.Select(line.Offset + fstEndTagIndex - 2, 0);
                                            }
                                        }
                                        e.Handled = true;
                                    }
                                }
                                #endregion

                            }
                            break;
                        }
                    case Key.Space:   // 这里只能这样处理才有效！goto 是无法解决问题的
                    case Key.Enter:   // 回车键
                        {
                            if (isCtrl && e.Key == Key.Enter)
                            {
                                if (!isShift)
                                {
                                    var textArea = this.TextArea;
                                    var doc = Document;

                                    if (textArea != null && textArea.IsKeyboardFocused)
                                    {
                                        #region 处理前导字符串
                                        // 为什么 Ctrl+Enter 不支持完整的前导字符串功能呢？一来没有必要，二来容易与 Ctrl+Enter 本身的功能冲突。

                                        // 以两个句点开头的行，表示忽略两个句点及其后的空白字符。换行时，应保留前两个句点及空白。
                                        var leaderTextRegs = @"^\.\.[ 　\t]*(\<[a-zA-Z][a-zA-Z0-9]{0,}[^\<\>]{0,}\/?\>)?";
                                        var fstSelectedLine = this.Document.GetLineByOffset(this.SelectionStart);
                                        var lineText = textArea.Document.GetText(fstSelectedLine.Offset, fstSelectedLine.Length);

                                        try
                                        {
                                            var regex = new Regex(leaderTextRegs);
                                            var match = regex.Match(lineText);
                                            if (match.Success && CustomMarkdownSupport.IsRegionSplitter(lineText) == false)
                                            {
                                                this.BeginChange();
                                                this.Select(fstSelectedLine.EndOffset, 0);
                                                if (match.Value.EndsWith("/>"))
                                                {
                                                    this.SelectedText = "\r\n" + match.Value;
                                                    this.Select(this.SelectionStart + this.SelectionLength, 0);
                                                }
                                                else
                                                {
                                                    var regHtmlTag = new Regex(@"(?<=\<)[a-zA-Z][a-zA-Z0-9]{0,}?(?=[ \/\>])");
                                                    var matchHtmlTag = regHtmlTag.Match(match.Value);
                                                    if (matchHtmlTag.Success)
                                                    {
                                                        var tailTag = $"</{matchHtmlTag.Value}>";
                                                        this.SelectedText = "\r\n" + match.Value + tailTag;
                                                        this.Select(this.SelectionStart + this.SelectionLength - tailTag.Length, 0);
                                                    }
                                                    else
                                                    {
                                                        this.SelectedText = "\r\n" + match.Value;
                                                        this.Select(this.SelectionStart + this.SelectionLength, 0);
                                                    }
                                                }
                                                this.EndChange();
                                                this.BringIntoView();

                                                e.Handled = true; return;
                                            }
                                        }
                                        catch (Exception ex)
                                        {
                                            LMessageBox.Show($"要保留的前导字符串正则表达式【{leaderTextRegs}】有错误，请重写正则表达式。\r\n\r\n　　" + ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                                        }
                                        #endregion

                                        //在当前行下方插入一个新行，并移动光标。
                                        AppendANewLine();
                                        JumpToNextCell();
                                        e.Handled = true;
                                    }
                                }
                                else
                                {
                                    // Ctrl+Shift+Enter时，如果当前行的文本符合下列规则：
                                    // 命令：命令名称：命令参数集
                                    // 或者符合下面的规则：
                                    // Cmd:cmd_name:cmd_parameters
                                    // 就将这些文本添加到 textCommands 列表中（加入前不保留头部的“命令：”（或“Cmd:”）。
                                    // 然后，在其它地方会检测这个列表，看有没有必要实现某些特定的功能。
                                    // 由于这个功能很容易让新手误会，所以不应该保存到配置文件中。只应在当前编辑器打开期间临时启用，且应可以取消。

                                    // - 暂时只想到按空格键时判断当前行的文本是否符合某个正则表达式，符合就换新行。
                                    // 命令如下：
                                    // 命令：空格键换行：正则表达式
                                    // 取消命令的方式：
                                    // 命令：空格键换行

                                    var lineText = this.CurrentLine();
                                    var regFillCommand = new Regex(@"([;；][ \t　]*?(命令)|(cmd))[:：][ \t　]*?((空格[符键]换行)|(space return))[ \t　]*?[:：].{0,}$");
                                    var matchCommand = regFillCommand.Match(lineText);
                                    if (matchCommand.Success)
                                    {
                                        var cmd = ParseCommand(lineText);
                                        if (cmd != null)
                                        {
                                            if (this.textCommands.ContainsKey(cmd.CommandName))
                                            {
                                                this.textCommands[cmd.CommandName] = cmd.Parameters;
                                            }
                                            else
                                            {
                                                this.textCommands.Add(cmd.CommandName, cmd.Parameters);
                                            }

                                            if (string.IsNullOrEmpty(cmd.Parameters))
                                            {
                                                LMessageBox.ShowInfomation("已取消临时命令！");
                                            }
                                            else
                                            {
                                                LMessageBox.ShowInfomation("已设置临时命令！");
                                            }

                                            e.Handled = true;
                                        }
                                    }

                                    var regWrapWithAntiQuotersCommand = new Regex(@"(?<=(([;；][ \t　]*?(命令)|(cmd))[ \t　]*?[:：][ \t　]*?((数字键?封装)|(wrap with number))[ \t　]*?[:：]))(开|on|关|off)?(?=[ \t　]*?$)");
                                    var matchWrapWithAntiQuotersCommand = regWrapWithAntiQuotersCommand.Match(lineText);
                                    if (matchWrapWithAntiQuotersCommand.Success)
                                    {
                                        // 注意：这个命令需要“开|关”两种状态，不带参数时，默认为关
                                        var wrapOnCmds = new List<string>() { "on", "开", };
                                        // var wrapOffCmds = new List<string>() { "off", "关", "" };
                                        if (wrapOnCmds.Contains(matchWrapWithAntiQuotersCommand.Value.ToLower()))
                                        {
                                            Globals.MainWindow.WrapByNumber = true;
                                            e.Handled = true;
                                            LMessageBox.ShowInfomation(
                                                "已开启数字键封装功能。此时按下数字键会自动尝试选取插入点光标向后对应个数的字符（不能跨行），并用反引号对(``)封装。\r\n" +
                                                "　　如果需要取消，请点击主窗口顶端右侧附加工具栏上对应按钮，或者设置反向命令：\r\n" +
                                                "　　；命令：数字键封装：关\r\n" +
                                                "注意：1. 输入 0 时实行弹性封装。\r\n" +
                                                "　　　2. 当前后封装字符串均设置为 ` 时，输入单个的 ` 也会执行弹性封装，不需要设置此命令。");
                                        }
                                        else
                                        {
                                            Globals.MainWindow.WrapByNumber = false;
                                            LMessageBox.ShowInfomation("已取消数字键封装功能！");
                                            e.Handled = true;
                                        }
                                    }

                                    regFillCommand = new Regex(@"^([;；][ \t　]*?(命令)|(cmd))[ \t　]*?[:：][ \t　]*?((填充(字符串)?(模板)?)|(fill( blank)?))[ \t　]*?$");
                                    var matchFillCommand = regFillCommand.Match(lineText);
                                    if (matchFillCommand.Success)
                                    {
                                        // 这种命令是立即执行的，与前面的“按下空格键再执行”是不同的，所以不需要调用 ParseCommand(...) 方法。
                                        // 基本思路：
                                        //     1. 从当前行向前倒找：字符串模板行、字符串填充数据行。
                                        //     2. 两行之间全部内容为模板（不含这两行本身，但包含空白行）；字符串填充行到当前行之间为填充数据（空白行忽略）。
                                        //     3. 用于填充的数据以 Tab 字符分割（空格保留）。

                                        var curLineNumber = this.CurrentDocumentLine().LineNumber;
                                        var tempStartNumber = GetLineNumberByRule(@"^[；;][ \t　]*?(字符串)?模板[ \t　]*?[：:]", curLineNumber, true);
                                        var fillDataStartNumber = GetLineNumberByRule(@"^[；;][ \t　]*?(填充)?数据[ \t　]*?[：:]", curLineNumber, true);

                                        var dataText = GetTextByLineNumbers(fillDataStartNumber + 1, curLineNumber - 1);
                                        var tempText = GetTextByLineNumbers(tempStartNumber + 1, fillDataStartNumber - 1);

                                        if (string.IsNullOrWhiteSpace(tempText))
                                        {
                                            LMessageBox.ShowWarning("未找到用于填充的字符串模板或数据！");
                                            e.Handled = true;
                                            return;
                                        }

                                        var tempLines = tempText.Split(new string[] { "\r\n" }, StringSplitOptions.None);
                                        var tempBuilder = new StringBuilder();
                                        foreach (var tempLine in tempLines)
                                        {
                                            if (tempLine.StartsWith("..") == false)
                                                tempBuilder.Append("..");
                                            tempBuilder.Append(tempLine);
                                            tempBuilder.Append("\r\n");
                                        }
                                        tempText = tempBuilder.ToString().Substring(0, tempBuilder.Length - 2);  //  最后一行多加了 \r\n

                                        if (string.IsNullOrWhiteSpace(tempText) || string.IsNullOrWhiteSpace(dataText))
                                        {
                                            LMessageBox.ShowWarning("未找到用于填充的字符串模板或数据！");
                                            e.Handled = true;
                                            return;
                                        }

                                        var textPieces = Utils.Text.SplitToPiecesByRegex(@"\{\{.*?\}\}", tempText);
                                        var feildsCount = 0;

                                        var matchedList = new List<TextPiece>();
                                        foreach (var tp in textPieces)
                                        {
                                            if (tp.IsMartchText)
                                            {
                                                matchedList.Add(tp);
                                                feildsCount++;
                                            }
                                        }

                                        if (feildsCount <= 0)
                                        {
                                            LMessageBox.ShowWarning("指定的字符串模板中未找到用于填充的标记区域！");
                                            e.Handled = true;
                                            return;
                                        }

                                        var table = new List<string[]>();
                                        var dataLinesArray = dataText.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                                        if (dataLinesArray.Length <= 0)
                                        {
                                            LMessageBox.ShowWarning("未提供用于填充的字符串数据！这些数据行内部应以管道符（|或｜）分隔。");
                                            e.Handled = true;
                                            return;
                                        }

                                        foreach (var lineTxt in dataLinesArray)
                                        {
                                            var fields = lineTxt.Split(new char[] { '|', '｜', }, StringSplitOptions.RemoveEmptyEntries);
                                            if (fields.Length > 0)
                                            {
                                                table.Add(fields);
                                            }
                                        }

                                        if (table.Count <= 0)
                                        {
                                            LMessageBox.ShowWarning("未提供用于填充的有效字符串数据！这些数据行内部应以管道符（“|”或“｜”）分隔，不能为空字符串。");
                                            e.Handled = true;
                                            return;
                                        }

                                        var sBuilder = new StringBuilder();
                                        foreach (var arr in table)
                                        {
                                            var x = Math.Min(arr.Length, feildsCount);
                                            for (int i = 0; i < x; i++)
                                            {
                                                matchedList[i].ReplacedText = arr[i].Trim();
                                            }

                                            // sBuilder.Append("..");    在这里加没用，因为模板是多行的。

                                            foreach (var tp in textPieces)
                                            {
                                                sBuilder.Append(tp.ReplacedText);
                                            }
                                            sBuilder.Append("\r\n");    // 这个最后一行也多加了 \r\n，但正好结尾标记行需要它，就没必要去除了。
                                        }

                                        var time = DateTime.Now.ToString();
                                        this.SelectedText = "\r\n\r\n[ 填充结果：" + time + "\r\n" + sBuilder.ToString() + "] 填充结果：" + time + "\r\n";
                                        this.Select(this.SelectionStart + 4, 0);
                                    }

                                    // 以 POST 或 GET 方式向指定网址上传数据。
                                    var regHttpCommand = new Regex(@"^([;；][ \t　]*?(命令)|(cmd))[ \t　]*?[:：][ \t　]*?(post|get)[ \t　]*?[:：]");
                                    var matchHttpCommand = regHttpCommand.Match(lineText);
                                    if (matchHttpCommand.Success)
                                    {
                                        var method = (matchHttpCommand.Value.ToLower().Contains("post") ? "POST" : "GET");

                                        var curLineNumber = this.CurrentDocumentLine().LineNumber;
                                        var httpDataStartNumber = GetLineNumberByRule(@"^[；;][ \t　]*?(http)?数据[ \t　]*?[:：]", curLineNumber, true);

                                        if (httpDataStartNumber < 0)
                                        {
                                            LMessageBox.ShowWarning("请在当前命令行前面准备数据！数据区域应以“；数据：xxx”行标记开端！");
                                            e.Handled = true;
                                            return;
                                        }

                                        var dataText = GetTextByLineNumbers(httpDataStartNumber + 1, curLineNumber - 1);

                                        var weburl = lineText.Substring(matchHttpCommand.Length).Trim();
                                        if (string.IsNullOrEmpty(weburl))
                                        {
                                            LMessageBox.ShowWarning("请在命令尾部输入目标网址！");
                                            e.Handled = true;
                                            return;
                                        }

                                        var lines = dataText.Replace("｜", "|").Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                                        if (lines.Length < 1)
                                        {
                                            LMessageBox.ShowWarning("数据应以二维文字表的形式提供，不能少于两行。第一行会被视为参数名称。");
                                            e.Handled = true;
                                            return;
                                        }

                                        List<string[]> list = new List<string[]>();
                                        var fstLine = lines[0];
                                        var fstLinePieces = fstLine.Trim(new char[] { ' ', '\t', '　', '|', }).Split(new char[] { '|', }, StringSplitOptions.RemoveEmptyEntries);
                                        if (fstLinePieces.Length < 1)
                                        {
                                            LMessageBox.ShowWarning("数据应以二维文字表的形式提供，不能少于两行。数据区没有提供有效的参数列表行。");
                                            e.Handled = true;
                                            return;
                                        }

                                        for (int i = 1; i < lines.Length; i++)
                                        {
                                            var line = lines[i];
                                            if (line.Contains("|") == false || CustomMarkdownSupport.IsColumnAlignmentDefinitionLine(line) ||
                                                CustomMarkdownSupport.IsTableCaptionSplittor(line)) continue;
                                            var pieces = line.Trim(new char[] { ' ', '\t', '　', '|', }).Split('|');
                                            if (pieces.Length < 1) continue;
                                            list.Add(pieces);
                                        }

                                        if (list.Count < 1)
                                        {
                                            LMessageBox.ShowWarning("数据应以二维文字表的形式提供，不能少于两行。数据区没有提供有效的数据行。");
                                            e.Handled = true;
                                            return;
                                        }

                                        var messages = new StringBuilder();
                                        foreach (var li in list)
                                        {
                                            var dict = new Dictionary<string, string>();
                                            for (int i = 0; i < fstLinePieces.Length; i++)
                                            {
                                                // 数据行中的字段如果没有对应的参数名，则忽略
                                                // 参数如果在数据行没有提供对应值，则默认为空字符串
                                                if (i < li.Length)
                                                {
                                                    dict.Add(fstLinePieces[i].Trim(), li[i].Trim());
                                                }
                                                else
                                                {
                                                    dict.Add(fstLinePieces[i].Trim(), "");
                                                }
                                            }

                                            string result;

                                            if (method == "POST")
                                            {
                                                result = L.HttpPost(weburl, dict, out string statusCode);
                                            }
                                            else
                                            {
                                                result = L.HttpGet(weburl, dict);
                                            }
                                            messages.Append(result + "\r\n");
                                        }

                                        LMessageBox.Show(messages.ToString());
                                        e.Handled = true;
                                        return;
                                    }
                                }
                            }
                            else
                            {
                                if (isShift && e.Key == Key.Enter)
                                {
                                    var textArea = this.TextArea;
                                    var doc = Document;

                                    #region 处理前导字符串
                                    // 为什么 Ctrl+Enter 不支持完整的前导字符串功能呢？一来没有必要，二来容易与 Ctrl+Enter 本身的功能冲突。

                                    // 以两个句点开头的行，表示忽略两个句点及其后的空白字符。换行时，应保留前两个句点及空白。
                                    var leaderTextRegs = @"^\.\.[ 　\t]*(\<[a-zA-Z][a-zA-Z0-9]{0,}[^\<\>]{0,}\/?\>)?";
                                    var fstSelectedLine = this.Document.GetLineByOffset(this.SelectionStart);
                                    var lineText = textArea.Document.GetText(fstSelectedLine.Offset, fstSelectedLine.Length);

                                    try
                                    {
                                        var regex = new Regex(leaderTextRegs);
                                        var match = regex.Match(lineText);
                                        if (match.Success && CustomMarkdownSupport.IsRegionSplitter(lineText) == false)
                                        {
                                            this.BeginChange();
                                            this.Select(fstSelectedLine.Offset, 0);   // 这个是在上方插入一行
                                            if (match.Value.EndsWith("/>"))
                                            {
                                                this.SelectedText = match.Value + "\r\n";
                                                this.Select(this.SelectionStart + this.SelectionLength - 2, 0);
                                            }
                                            else
                                            {
                                                var regHtmlTag = new Regex(@"(?<=\<)[a-zA-Z][a-zA-Z0-9]{0,}?(?=[ \/\>])");
                                                var matchHtmlTag = regHtmlTag.Match(match.Value);
                                                if (matchHtmlTag.Success)
                                                {
                                                    var tailTag = $"</{matchHtmlTag.Value}>";
                                                    this.SelectedText = match.Value + tailTag + "\r\n";
                                                    this.Select(this.SelectionStart + this.SelectionLength - tailTag.Length - 2, 0);
                                                }
                                                else
                                                {
                                                    this.SelectedText = match.Value + "\r\n";
                                                    this.Select(this.SelectionStart + this.SelectionLength - 2, 0);
                                                }
                                            }
                                            this.EndChange();
                                            this.BringIntoView();

                                            e.Handled = true; return;
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        LMessageBox.Show($"要保留的前导字符串正则表达式【{leaderTextRegs}】有错误，请重写正则表达式。\r\n\r\n　　" + ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                                    }
                                    #endregion

                                    //在当前行上方插入一个新行，并移动光标。
                                    InsertANewLine();
                                    JumpToNextCell();
                                    e.Handled = true;
                                }
                                else
                                {
                                    if (e.Key == Key.Enter)
                                    {
                                        //AvalonEdit有个默认行为：若一行文本以空格开头，无论在第一个非空格字符前按下回车，还是在这些空格前按下回车，
                                        //新行开头都不会保留空格！！！
                                        RunPythonScriptsWhenEnterPressing(out bool cancel);
                                        if (cancel)
                                        {
                                            e.Handled = true;
                                            return;
                                        }
                                    }

                                    if (IsCompleting == false)
                                    {
                                        var enableSpace = false;
                                        var cmdParameter = FindTextCommandParameter("space-as-enter");
                                        if (string.IsNullOrEmpty(cmdParameter) == false)
                                        {
                                            var regex = new Regex(cmdParameter);
                                            var curLineLeftText = this.LeftText;
                                            var match = regex.Match(curLineLeftText);
                                            if (match.Success)
                                                enableSpace = true;
                                        }

                                        if (enableSpace == false && e.Key == Key.Space) break;

                                        var textArea = this.TextArea;
                                        var doc = Document;

                                        #region 处理方块区域头的自动格式化

                                        var fstLine = fst_doc_ln;
                                        var fstLineText = this.Document.GetText(fstLine.Offset, fstLine.Length);
                                        if (fstLineText.StartsWith("【") && fstLineText.Contains("】") == false)
                                        {
                                            this.Document.Replace(fstLine.Offset, fstLine.Length, "[ " + fstLineText.TrimStart(new char[] { ' ', '　', '\t', '【' }) + "\r\n");
                                            this.Select(this.Document.GetLineByNumber(fstLine.LineNumber + 1).Offset, 0);
                                            e.Handled = true;
                                            return;
                                        }
                                        else if (fstLineText.Trim().StartsWith("】"))
                                        {
                                            this.Document.Replace(fstLine.Offset, fstLine.Length, "] " + fstLineText.TrimStart(new char[] { ' ', '　', '\t', '】' }) + "\r\n");
                                            this.Select(this.Document.GetLineByNumber(fstLine.LineNumber + 1).Offset, 0);
                                            e.Handled = true;
                                            return;
                                        }

                                        #endregion

                                        #region 处理多标点标记
                                        // 这里只需要处理结对是多句号、多叹号、多问号三种情况，其它双冒号、双逗号都不需要处理。
                                        var oldSelStart = this.SelectionStart;
                                        var regexMuiltyPunctuationMark = new Regex(@"(。{2,}$)|([?？]{2,}$)|([!！]{2,}$)");
                                        if (regexMuiltyPunctuationMark.Match(left_text).Success)
                                        {
                                            this.SelectedText = "\r\n\r\n^ ";
                                            this.Select(oldSelStart + 6, 0);
                                            e.Handled = true;
                                            return;
                                        }

                                        #endregion

                                        #region 处理单行自定义折叠区向常规折叠区的转换
                                        var selectedText = this.Document.GetText(this.SelectionStart, this.SelectionLength);
                                        if (selectedText.Contains("\r\n") == false)
                                        {
                                            var selLine = this.Document.GetLineByOffset(this.SelectionStart);
                                            var selLineText = this.Document.GetText(selLine.Offset, selLine.Length);
                                            var selStart = SelectionStart;
                                            var selLength = SelectionLength;
                                            if (selLine.EndOffset >= selStart + selLength)  // 右边至少有个 }
                                            {
                                                var rightText = this.Document.GetText(selStart, selLine.EndOffset - selStart);
                                                var regSingleLineRegion = new Regex(@"^[ 　]{0,3}\{(?=([ 　\t]{0,}?[\+\-]?[ 　\t]{0,}?[:：]?[ 　\t]{0,}?[\[【][^\[\]【】]*[\]】]))");
                                                if (regSingleLineRegion.Match(LeftText).Success)
                                                {
                                                    if (rightText.TrimEnd(new char[] { ' ', '　', '\t', }).EndsWith("}"))
                                                    {
                                                        rightText = rightText.Substring(0, rightText.Length - 1);  // 注意：这里只需要去除最后一个 } 即可！不能直接用 Trim() 方法。
                                                        this.Select(this.SelectionStart, 0);
                                                        var replaceText = rightText.Trim(new char[] { ' ', '　', '\t', });
                                                        this.Document.Replace(this.SelectionStart, selLine.EndOffset - this.SelectionStart, $"\r\n\r\n{replaceText}\r\n\r\n}}\r\n");
                                                        this.Select(this.SelectionStart + 4 + replaceText.Length, 0);
                                                        e.Handled = true;
                                                        break;
                                                    }
                                                }
                                            }
                                        }
                                        #endregion

                                        #region 处理 Html Tag 的自动换行/缩进功能

                                        if (this.SelectionLength == 0)
                                        {
                                            var oldSelOffset = SelectionStart;
                                            var line = doc.GetLineByOffset(oldSelOffset);
                                            var lineText = doc.GetText(line.Offset, line.Length);
                                            var selPoint = oldSelOffset - line.Offset;
                                            var leftText = lineText.Substring(0, selPoint);
                                            var rightText = lineText.Substring(selPoint);
                                            string tagName;
                                            string preFixText;
                                            if (IsEndWithHtmlTag(leftText, out tagName, out preFixText) && rightText.ToLower().StartsWith($"</{tagName.ToLower()}>"))
                                            {
                                                var replaceText = "\r\n";
                                                if (lineText.StartsWith(".."))
                                                {
                                                    var preFixBlanks = new string(' ', Utils.Text.GetTextWidth(preFixText, false) - 2);  // -2 是给行头的两个句点留下位置
                                                    replaceText += ".." + preFixBlanks;
                                                    replaceText += "    \r\n..";               //两个句点开头的行不用担心被当成代码块！
                                                    replaceText += preFixBlanks;
                                                    doc.Replace(oldSelOffset, 0, replaceText);
                                                    Select(oldSelOffset + preFixBlanks.Length + 8, 0);
                                                }
                                                else
                                                {
                                                    var preFixBlanks = new string(' ', Utils.Text.GetTextWidth(preFixText, false));
                                                    replaceText += preFixBlanks;
                                                    replaceText += "\r\n";               //为什么不能自动缩进？因为在 Markdown 中以四个空格开头的行会被当成代码块！
                                                    replaceText += preFixBlanks;
                                                    doc.Replace(oldSelOffset, 0, replaceText);
                                                    Select(oldSelOffset + preFixBlanks.Length + 2, 0);
                                                }
                                                e.Handled = true;
                                                break;
                                            }
                                            //break; //放在这里会导致树型文字表编辑功能失效。
                                        }
                                        #endregion

                                        #region 处理树型列表
                                        var curLine = CurrentDocumentLine();
                                        var curLineText = this.Document.GetText(curLine.Offset, curLine.Length);
                                        string header, tail; int level;
                                        if (curLine != null && CustomMarkdownSupport.IsStartsWithImageLinkLine(curLineText) == false &&
                                            IsTreeListLine(curLineText, out header, out tail, out level))
                                        {
                                            e.Handled = true;
                                            InsertNewLineAndFormat(isShift);
                                            break;
                                        }
                                        #endregion

                                        #region 处理前导字符串
                                        if (textArea != null && textArea.IsKeyboardFocused)
                                        {
                                            var fstSelectedLine = this.Document.GetLineByOffset(this.SelectionStart);

                                            //回车换行时保留的前导字串：
                                            //^[\t 　☆○◎◇□△§※★●◆■▲>〉》]{ 1,}[ \t]*
                                            //处理用户自定义的前导字符。
                                            //顺序是：先A类，再B类，最后内置前导字符。基本原则：用户定义优先。

                                            var leaderTextRegs = Globals.MainWindow.LeaderTextRegsA;
                                            if (string.IsNullOrEmpty(leaderTextRegs) == false)
                                            {
                                                leaderTextRegs = ConvertToRegexTexts(leaderTextRegs);

                                                if (ReplaceLeaderText(leaderTextRegs, false))
                                                {
                                                    e.Handled = true; return;
                                                }
                                            }

                                            leaderTextRegs = Globals.MainWindow.LeaderTextRegsB;
                                            if (string.IsNullOrWhiteSpace(leaderTextRegs) == false)
                                            {
                                                leaderTextRegs = ConvertToRegexTexts(leaderTextRegs);

                                                if (ReplaceLeaderText(leaderTextRegs, true))
                                                {
                                                    e.Handled = true; return;
                                                }
                                            }

                                            leaderTextRegs = @"^[\^\t 　☆○◎◇□△§※★●◆■▲>〉》]{1,}[ 　\t]*";
                                            if (ReplaceLeaderText(leaderTextRegs, false))
                                            {
                                                e.Handled = true; return;
                                            }

                                            leaderTextRegs = @"^[" + Globals.MainWindow.ReplaceLeaderChar + "] {0,}";
                                            //默认情况下，以替换式自动添加的前导字串总是“$+空格串”样式的，这是为了便于连续输入“$+空格串”的新行。
                                            //但是用户可以指定另一字符。

                                            if (ReplaceLeaderText(leaderTextRegs, false))
                                            {
                                                e.Handled = true; return;
                                            }

                                            //按倍数放大文本
                                            leaderTextRegs = @"^[ 　]{0,3}<[1234567890１２３４５６７８９０]{1,}([.。][1234567890１２３４５６７８９０]{1,}){0,}>[ 　\t]*";
                                            if (ReplaceLeaderText(leaderTextRegs, false))
                                            {
                                                e.Handled = true; return;
                                            }

                                            //无序列表项
                                            leaderTextRegs = @"^[ 　]{0,3}(([\-\+－＋][ 　\t]*)|([\*＊][ 　\t]+))";
                                            if (ReplaceLeaderText(leaderTextRegs, false))
                                            {
                                                e.Handled = true; return;
                                            }

                                            // 以两个句点开头的行，表示忽略两个句点及其后的空白字符。换行时，应保留前两个句点及空白。
                                            var lineText = this.Document.GetText(fstSelectedLine.Offset, fstSelectedLine.Length);
                                            if (CustomMarkdownSupport.IsRegionSplitter(lineText) == false)
                                            {
                                                leaderTextRegs = @"^\.\.[ 　\t]*";
                                                if (ReplaceLeaderText(leaderTextRegs, false))
                                                {
                                                    e.Handled = true; return;
                                                }
                                            }
                                        }
                                        #endregion

                                        // 这个不能放在前面
                                        if (e.Key == Key.Space)
                                        {
                                            e.Handled = true;
                                            this.TextArea.Selection.ReplaceSelectionWithText("\r\n");
                                        }
                                    }
                                }
                                //这个方法会造成无法自动滚动的问题。
                            }
                            break;
                        }
                    case Key.Tab://Tab键
                        {
                            if (!isCtrl && !isAlt)
                            {
                                if (IsCompleting) break;   // 开启自动完成时，此功能不应起作用！

                                var curLine = CurrentDocumentLine();
                                if (curLine.LineNumber > 1 || CustomMarkdownSupport.IsTaskLine(CurrentLine()) == false)
                                // 第 1 行通常是完成状态行，不需要跳转，反而经常需要用 Tab 来自动将 Date 转换成日期。
                                {
                                    if (JumpToNextExamElement(isShift))
                                    {
                                        e.Handled = true;
                                        break;
                                    }
                                }

                                if (isShift)
                                {
                                    if (JumpToPreviewCell())
                                    {
                                        e.Handled = true;
                                    }
                                    else
                                    {
                                        switch (Globals.MainWindow.TabKeyMode)
                                        {
                                            case TabKeyMode.JumpBetweenTags:
                                                {
                                                    if (JumpBetweenMarks(ref e, isShift)) return;

                                                    break;
                                                }
                                            case TabKeyMode.JumpOverTag:
                                                {
                                                    if (JumpOverMarks(ref e, isShift)) return;

                                                    //2017年1月11日，调整到这里是为了让树型文字表中的括号对啥的也支持使用Tab键来越过去。
                                                    #region 处理树型列表的层级调整
                                                    if (MoveTreeListTableLevel(e, isShift)) return;
                                                    #endregion

                                                    break;
                                                }
                                            case TabKeyMode.RunScript:
                                                {
                                                    RunPythonScriptsWhenTabPressing(out bool cancel);
                                                    e.Handled = cancel;
                                                    return;
                                                }
                                            default:
                                                {
                                                    e.Handled = false;
                                                    break;
                                                }
                                        }
                                    }
                                }
                                else
                                {
                                    if (JumpToNextCell())
                                    {
                                        e.Handled = true;
                                    }
                                    else
                                    {
                                        #region 输入后自动替换

                                        //TODO: 可以考虑实现这样的功能：
                                        //      输入如下文本：
                                        //          今天的日期是date
                                        //      然后按 Tab 键，自动变成：
                                        //          今天的日期是2018年3月15日
                                        if (AutoReplaceManager.Replace(Document, this.SelectionStart, this.SelectionLength))
                                        {
                                            e.Handled = true;
                                            return;
                                        }

                                        #endregion

                                        switch (Globals.MainWindow.TabKeyMode)
                                        {
                                            case TabKeyMode.JumpBetweenTags:
                                                {
                                                    if (JumpBetweenMarks(ref e, isShift)) return;
                                                    break;
                                                }
                                            case TabKeyMode.JumpOverTag:
                                                {
                                                    //跳过反引号，用在：按Ctrl+`输入一对反引号并将插入点置于两个反引号之间，输入一些文本后直接跳过右反引号继续输入其它文本。
                                                    //这比按两个组合方向按键跳过去或者按方向键跳过去更方便。
                                                    //*号对与_对情形类似。

                                                    if (JumpOverMarks(ref e, isShift)) return;

                                                    //2017年1月11日，调整到这里是为了让树型文字表中的括号对啥的也支持使用Tab键来越过去。
                                                    #region 处理树型列表的层级调整
                                                    if (MoveTreeListTableLevel(e, isShift)) return;
                                                    #endregion

                                                    break;
                                                }
                                            case TabKeyMode.RunScript:
                                                {
                                                    RunPythonScriptsWhenTabPressing(out bool cancel);
                                                    e.Handled = cancel;
                                                    break;
                                                }
                                            default:
                                                {
                                                    e.Handled = false;   // 仍然输入Tab好了。
                                                    break;
                                                }
                                        }

                                    }
                                }
                            }
                            break;
                        }
                    case Key.Z:
                        {
                            if (isAlt)
                            {
                                if (!isShift && isCtrl)
                                {
                                    if (Globals.MainWindow.SupportFolding)
                                    {
                                        this.FoldSelectedBlock();
                                    }
                                }
                            }
                            else
                            {
                                if (!isShift && isCtrl)
                                {
                                    if (this.CanUndo) this.Undo();
                                    e.Handled = true;
                                }
                            }
                            break;
                        }
                    case Key.Y:
                        {
                            if (!isAlt && !isShift && isCtrl)
                            {
                                if (this.CanRedo) this.Redo();
                                e.Handled = true;
                            }
                            break;
                        }
                    case Key.OemPeriod://句点键
                        {
                            if (isShift && isCtrl && !isAlt)
                            {
                                var fstSelLine = this.Document.GetLineByOffset(this.SelectionStart);
                                var lastSelLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);

                                this.BeginChange();

                                for (int lineNumber = fstSelLine.LineNumber; lineNumber <= lastSelLine.LineNumber; lineNumber++)
                                {
                                    var line = this.Document.GetLineByNumber(lineNumber);
                                    var lineText = this.Document.GetText(line);

                                    lineText = "> " + lineText;
                                    this.Document.Replace(line.Offset, line.Length, lineText);
                                }

                                this.EndChange();
                            }
                            break;
                        }
                    case Key.OemComma://逗号健
                        {
                            if (isShift && isCtrl && !isAlt)
                            {
                                var fstSelLine = this.Document.GetLineByOffset(this.SelectionStart);
                                var lastSelLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);

                                Regex reg = new Regex(@"^[ 　]{0,3}[>〉》][ 　]{0,}");

                                this.BeginChange();

                                for (int lineNumber = fstSelLine.LineNumber; lineNumber <= lastSelLine.LineNumber; lineNumber++)
                                {
                                    var line = this.Document.GetLineByNumber(lineNumber);
                                    var lineText = this.Document.GetText(line);
                                    var match = reg.Match(lineText);
                                    if (match.Success)
                                    {
                                        lineText = lineText.Substring(match.Length);
                                        this.Document.Replace(line.Offset, line.Length, lineText);
                                    }
                                }

                                this.EndChange();
                            }
                            break;
                        }
                    case Key.C:
                        {
                            if (isCtrl && !isAlt)
                            {
                                if (isShift)
                                {
                                    if (this.SelectedText.Length <= 0)
                                    {
                                        // 复制当前行到新行
                                        var selLine = this.Document.GetLineByOffset(this.SelectionStart);
                                        var selLineText = this.Document.GetText(selLine.Offset, selLine.Length);
                                        var oldSelOffset = this.SelectionStart - selLine.Offset;
                                        if (selLineText.EndsWith("\r\n"))
                                        {
                                            this.Select(selLine.EndOffset, 0);
                                            this.SelectedText = selLineText;
                                            this.Select(this.SelectionStart + oldSelOffset, 0);
                                        }
                                        else
                                        {
                                            this.Select(selLine.EndOffset, 0);
                                            this.SelectedText = "\r\n" + selLineText;
                                            this.Select(this.SelectionStart + oldSelOffset + 2, 0);
                                        }
                                        e.Handled = true;
                                    }
                                }
                                else
                                {
                                    if (this.SelectedText.Length <= 0)
                                    {
                                        var selLine = this.Document.GetLineByOffset(this.SelectionStart);
                                        var selLineText = this.Document.GetText(selLine.Offset, selLine.Length);
                                        if (IsExamLine(selLineText, out int headerMarkLength))
                                        {
                                            // Clipboard.SetData(DataFormats.UnicodeText, selLineText.Substring(headerMarkLength));
                                            Clipboard.SetDataObject(selLineText.Substring(headerMarkLength));

                                            e.Handled = true;
                                        }
                                    }
                                }
                            }
                            break;
                        }
                    case Key.X:
                        {
                            if (isCtrl && !isShift && !isAlt)
                            {
                                if (this.SelectedText.Length <= 0)
                                {
                                    var selLine = this.Document.GetLineByOffset(this.SelectionStart);
                                    var selLineText = this.Document.GetText(selLine.Offset, selLine.Length);
                                    if (IsExamLine(selLineText, out int headerMarkLength))
                                    {
                                        // Clipboard.SetData(DataFormats.UnicodeText, selLineText.Substring(headerMarkLength));
                                        Clipboard.SetDataObject(selLineText.Substring(headerMarkLength));

                                        this.Document.Replace(selLine.Offset + headerMarkLength, selLine.Length - headerMarkLength, "");
                                        e.Handled = true;
                                    }
                                }
                            }
                            break;
                        }
                    case Key.D:
                        {
                            if (!isShift && isCtrl && !isAlt)
                            {
                                var textArea = this.TextArea;
                                if (textArea != null && textArea.IsKeyboardFocused)
                                {
                                    var curLine = this.Document.GetLineByOffset(this.SelectionStart);
                                    var curLineText = this.Document.GetText(curLine.Offset, curLine.Length);

                                    if (IsExamLine(curLineText, out int headMarkLength))
                                    {
                                        var offset = curLine.Offset + headMarkLength;
                                        this.Select(offset, curLine.EndOffset - offset);
                                        textArea.PerformTextInput("");
                                        e.Handled = true;
                                    }
                                }
                            }
                            break;
                        }
                        //case Key.Delete:
                        //    {
                        //        if(isShift && !isCtrl && !isAlt)
                        //        {
                        //            // Shift + Del 删除行在这里无效！
                        //        }
                        //        break;
                        //    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show("发生意外错误，无法执行操作。建议保存文件并重启程序。\r\n" +
                    ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 获取指定行号之间的文本。
        /// </summary>
        /// <param name="startLineNumber">从哪行开始（含这一行）。</param>
        /// <param name="endLineNumber">到哪行结束（含这一行）。</param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public string GetTextByLineNumbers(int startLineNumber, int endLineNumber)
        {
            if (startLineNumber > endLineNumber)
            {
                var temp = startLineNumber;
                startLineNumber = endLineNumber;
                endLineNumber = temp;
            }

            if (startLineNumber < 1 || endLineNumber > this.LineCount) return null;

            var startOffset = this.GetLineByNumber(startLineNumber).Offset;
            var endOffset = this.GetLineByNumber(endLineNumber).EndOffset;
            return this.GetText(startOffset, endOffset - startOffset);
        }

        public string 取两行之间的文本(int startLineNumber, int endLineNumber)
        {
            return GetTextByLineNumbers(startLineNumber, endLineNumber);
        }

        public PythonList 取两行之间的文本行列表(int startLineNumber, int endLineNumber, bool ignoreEmpty = true)
        {
            var text = this.取两行之间的文本(startLineNumber, endLineNumber);
            if (string.IsNullOrEmpty(text)) return null;

            var lines = text.Split(new string[] { "\r\n", "\n", "\r" }, ignoreEmpty ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None);
            var list = new PythonList();
            foreach (var line in lines)
            {
                list.Add(line);
            }
            return list;
        }

        /// <summary>
        /// 按正则表达式向后或向前查找符合正则表达式要求的文本行的行号。
        /// 注意：当前行不进行匹配。如果要从第5行向前倒找，应传入6。
        /// </summary>
        /// <param name="rule">正则表达式</param>
        /// <param name="curLineNumber">从哪一行开始找。</param>
        /// <param name="backword">是否倒找。默认为 false。</param>
        /// <returns>返回找到的行号。找不到返回 -1。</returns>
        public int GetLineNumberByRule(string rule, int curLineNumber, bool backword = false)
        {
            if (string.IsNullOrEmpty(rule)) return -1;

            if (backword)
            {
                // 向前倒找
                if (curLineNumber <= 1) return -1;
                var startNumber = curLineNumber - 1;
                if (startNumber > this.LineCount)
                    startNumber = this.LineCount;

                Regex reg = new Regex(rule);
                for (int i = startNumber; i >= 1; i--)
                {
                    var line = this.GetLineByNumber(i);
                    var lineText = this.GetText(line.Offset, line.Length);
                    var match = reg.Match(lineText);
                    if (match.Success)
                    {
                        return i;
                    }
                }
                return -1;
            }
            else
            {
                // 向后找
                if (curLineNumber >= this.LineCount) return -1;
                var startNumber = curLineNumber + 1;
                if (startNumber < 1)
                    startNumber = 1;

                Regex reg = new Regex(rule);
                for (int i = startNumber; i <= this.LineCount; i++)
                {
                    var line = this.GetLineByNumber(i);
                    var lineText = this.GetText(line.Offset, line.Length);
                    var match = reg.Match(lineText);
                    if (match.Success)
                    {
                        return i;
                    }
                }
                return -1;
            }
        }

        public int 按规则查找文本行号(string rule, int curLineNumber, bool backword = false)
        {
            return GetLineNumberByRule(rule, curLineNumber, backword);
        }

        private string FindTextCommandParameter(string v)
        {
            try
            {
                return this.textCommands[v];
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// 转换文本命令。
        /// </summary>
        /// <param name="lineText"></param>
        /// <returns></returns>
        private TextCommand ParseCommand(string lineText)
        {
            var regex = new Regex(@"(?<=([;；](命令)|(cmd))[:：])((空格[符键]换行)|(space return))[:：].{0,}$");
            var match = regex.Match(lineText);
            if (match.Success == false) return null;

            var pieces = Utils.Text.SplitToPiecesByRegex(@"[:：]", match.Value);
            if (pieces.Count < 1) return null;

            var txtCmd = new TextCommand();
            txtCmd.CommandName = "space-as-enter";
            if (pieces.Count <= 1)
            {
                txtCmd.Parameters = "";
            }
            else
            {
                txtCmd.Parameters = match.Value.Substring(pieces[0].Length + 1);
            }
            return txtCmd;
        }

        internal void RunPythonScriptsWhenTabPressing(out bool cancel)
        {
            cancel = false;
            try
            {
                var list = new List<UIElement>();
                foreach (var item in Globals.MainWindow.lbxPythonScripts.Items)
                {
                    var ue = item as UIElement;
                    if (ue != null) list.Add(ue);
                }

                foreach (var item in Globals.MainWindow.lbxWorkspacePythonScripts.Items)
                {
                    var ue = item as UIElement;
                    if (ue != null) list.Add(ue);
                }

                for (int i = 0; i < list.Count; i++)
                {
                    var item = list[i];
                    var psli = item as PythonScriptListItem;
                    if (psli == null) continue;
                    var shortCutText = psli.ShortCutText.ToLower();
                    if (psli.CallMode != PythonScriptCallMode.TabPressing && shortCutText != "tab" && shortCutText != "table") continue;

                    PythonResult pr = PythonScriptListItem.RunScript(psli.CallMode, psli.FullPath, false);
                    if (pr.Cancel)
                    {
                        cancel = true;  // 只要有一个就取消
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning(ex.Message);
            }
        }

        internal void RunPythonScriptsWhenEnterPressing(out bool cancel)
        {
            cancel = false;
            try
            {
                var list = new List<UIElement>();
                foreach (var item in Globals.MainWindow.lbxPythonScripts.Items)
                {
                    var ue = item as UIElement;
                    if (ue != null) list.Add(ue);
                }

                foreach (var item in Globals.MainWindow.lbxWorkspacePythonScripts.Items)
                {
                    var ue = item as UIElement;
                    if (ue != null) list.Add(ue);
                }

                for (int i = 0; i < list.Count; i++)
                {
                    var item = list[i];
                    var psli = item as PythonScriptListItem;
                    if (psli == null) continue;
                    var shortCutText = psli.ShortCutText.ToLower();
                    if (psli.CallMode != PythonScriptCallMode.EnterPressing && shortCutText != "enter" && shortCutText != "return") continue;

                    PythonResult pr = PythonScriptListItem.RunScript(psli.CallMode, psli.FullPath, false);

                    if (string.IsNullOrWhiteSpace(pr.ErrInfo) == false)
                    {
                        LMessageBox.ShowWarning(pr.ErrInfo);
                    }

                    if (pr.Cancel)
                    {
                        cancel = true;  // 只要有一个就取消
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning(ex.Message);
            }
        }

        /// <summary>
        /// 判断当前行是否是试题使用的行。
        /// </summary>
        /// <param name="line"></param>
        /// <param name="headMarkLength"></param>
        /// <returns></returns>
        public static bool IsExamLine(string line, out int headMarkLength)
        {
            if (string.IsNullOrWhiteSpace(line))
            {
                headMarkLength = 0; return false;
            }

            Regex regEx = new Regex(@"^((　　(试题|答案|错项|解析))|(＜＜(材料|出处|问题)))＞＞");
            var match = regEx.Match(line);
            if (match.Success == false)
            {
                headMarkLength = 0;
                return false;
            }

            headMarkLength = match.Length;
            return true;
        }

        public string FormatHtmlSpan(string html)
        {
            NSoup.Nodes.Document doc = NSoup.NSoupClient.ParseBodyFragment(html);

            var result = doc.Body.Html();
            var lines = result.Split(new string[] { "\n", }, StringSplitOptions.None);  // NSoup 格式化出来的都是 \n
            var sb = new StringBuilder();
            foreach (var line in lines)
            {
                var spaceCount = 0;
                foreach (var c in line)
                {
                    if (c == ' ') { sb.Append("    "); spaceCount++; continue; }
                    else break;
                }

                if (spaceCount > 0)
                {
                    sb.Append(line.Substring(spaceCount).TrimEnd());
                    sb.Append("\r\n");
                    continue;
                }
                else
                {
                    sb.Append(line.TrimEnd());
                    sb.Append("\r\n");
                    continue;
                }
            }

            return sb.ToString();
        }

        public string 格式化超文本片段(string html)
        {
            return FormatHtmlSpan(html);
        }

        public static bool IsExistHtml(string src)
        {
            string reg = "<[/]?[a-zA-Z].*?>";
            if (Regex.IsMatch(src, reg)) return true;
            return false;
        }

        public bool IsEmmetCode(string text)
        {
            if (IsExistHtml(text)) return false;
            var reg = new Regex(@"[ 　]{0,3}[a-zA-Z0-9\-_\>!\+\^\*\(\)#\[\]\{\}\$\@\=\.]{1,}");
            return reg.Match(text).Success;
        }

        /// <summary>
        /// 注意，它与 MarkdownEditor 类下的 IsEndOfHtmlTag() 方法的计算方法是不同的。
        /// </summary>
        /// <param name="leftText"></param>
        /// <param name="tagName"></param>
        /// <param name="preFixText"></param>
        /// <returns></returns>
        private static bool IsEndWithHtmlTag(string leftText, out string tagName, out string preFixText)
        {
            if (string.IsNullOrWhiteSpace(leftText))
            {
                tagName = ""; preFixText = ""; return false;
            }

            //这里用正则太复杂，要考虑反向、Attribute 值带尖括号等特殊情况，
            //还是就简单点好了——反正平时我也用不到这些特殊情况。

            var preIndex = leftText.LastIndexOf('<');
            if (preIndex < 0) { tagName = ""; preFixText = ""; return false; }
            var nextCharIndex = preIndex + 1;
            if (nextCharIndex >= leftText.Length) { tagName = ""; preFixText = ""; return false; }

            var nextChar = leftText[nextCharIndex];
            if (nextChar == ' ' || nextChar == '　' || nextChar == '\t') { tagName = ""; preFixText = ""; return false; }

            var nextBlankIndex = leftText.IndexOf(' ', nextCharIndex);
            if (nextBlankIndex < 0)
            {
                tagName = leftText.Substring(preIndex + 1, leftText.Length - preIndex - 2);
                preFixText = leftText.Substring(0, preIndex);
                return true;
            }

            tagName = leftText.Substring(preIndex + 1, nextBlankIndex - preIndex - 1);
            preFixText = leftText.Substring(0, preIndex);
            return true;
        }

        /// <summary>
        /// 保存的正则表达式是多行的，每行一个规则，这个方法是用来将这些规则合并为一个规则字串的。
        /// 合并后的样式类似：(规则一)|(规则二)|(规则三)
        /// </summary>
        /// <param name="leaderTextRegs"></param>
        /// <returns></returns>
        private static string ConvertToRegexTexts(string leaderTextRegs)
        {
            var spans = leaderTextRegs.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);

            if (spans.Length > 0)
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("(");
                foreach (var span in spans)
                {
                    sb.Append(span);
                    sb.Append(")|(");
                }
                leaderTextRegs = sb.ToString();
                if (leaderTextRegs.EndsWith("|("))
                    leaderTextRegs = leaderTextRegs.Substring(0, leaderTextRegs.Length - 2);
            }

            return leaderTextRegs;
        }

        /// <summary>
        /// 按Enter时在新行头部替换添加“前导字符串”。
        /// </summary>
        /// <param name="regexText">当前行头部前导字符的正则表达式规则。</param>
        /// <param name="replaceMode">是否替换为“$+空格串”的样式。为false时原文输出。</param>
        /// <returns></returns>
        private bool ReplaceLeaderText(string regexText, bool replaceMode)
        {
            if (string.IsNullOrEmpty(regexText)) return false;
            var textArea = this.TextArea;
            var fstSelectedLine = this.Document.GetLineByOffset(this.SelectionStart);
            var lineText = textArea.Document.GetText(fstSelectedLine.Offset, fstSelectedLine.Length);
            if (CustomMarkdownSupport.IsHorizontalLineText(lineText)) return false;

            try
            {
                var regex = new Regex(regexText);
                var match = regex.Match(lineText);
                if (match.Success == false) return false;

                if (match.Value == lineText)//如果只添加了标记，却没有其它文本内容，直接取消标记。
                {
                    this.BeginChange();
                    this.Select(fstSelectedLine.Offset, this.SelectionStart - fstSelectedLine.Offset + this.SelectionLength);
                    this.SelectedText = "\r\n";
                    this.Select(fstSelectedLine.Offset + 2, 0);
                    this.EndChange();
                    this.BringIntoView();
                }
                else
                {
                    if (replaceMode)
                    {
                        //将匹配的文本转换为半角字符组成的等字符宽度字串
                        //一个全角字符被视为两个半角空格的字符宽度
                        //这里的“字符宽度”只考虑全、半角，不考虑字体带来的宽度变化

                        string spaceText;
                        var length = Utils.Text.GetTextWidth(match.Value);
                        if (length > 1)
                        {
                            spaceText = Globals.MainWindow.ReplaceLeaderChar + new string(' ', length - 1);
                        }
                        else { spaceText = Globals.MainWindow.ReplaceLeaderChar; }

                        textArea.PerformTextInput($"\r\n{spaceText}");
                    }
                    else
                    {
                        //当前行符合规则的前导字串原文添加到新行头部。
                        if (match.Value == "^ " && lineText.EndsWith("  ") == false)
                        {
                            textArea.PerformTextInput($"  \r\n{match.Value}");
                        }
                        else
                        {
                            if (fstSelectedLine.Offset == SelectionStart && lineText.StartsWith(match.Value))
                            {
                                textArea.PerformTextInput($"\r\n");
                            }
                            else
                            {
                                textArea.PerformTextInput($"\r\n{match.Value}");
                            }
                        }
                    }
                    textArea.BringIntoView();
                }
                return true;
            }
            catch (Exception ex)
            {
                LMessageBox.Show($"要保留的前导字符串正则表达式【{regexText}】有错误，请重写正则表达式。\r\n\r\n　　" + ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return false;
            }
        }

        /// <summary>
        /// 改变树型文字表行的层级。
        /// </summary>
        /// <param name="isShift">为真时（按Shift键+Tab键）层级减小一级（向左）；
        /// 为假时（只按Tab键）层级增加一级（向右）。</param>
        private bool MoveTreeListTableLevel(KeyEventArgs e, bool isShift)
        {
            var curLine = CurrentDocumentLine();
            string header, tail; int level;
            if (curLine != null && IsTreeListLine(this.Document.GetText(curLine.Offset, curLine.Length), out header, out tail, out level))
            {
                e.Handled = true;
                if (isShift) MoveTreeListLineLevelLeft(curLine, header, tail, level);
                else MoveTreeListLineLevelRight(curLine, header, tail, level);
                return true;
            }

            return false;
        }

        #region 处理树型列表


        /// <summary>
        /// 是否树型列表文本行。
        /// </summary>
        /// <param name="line">要判断的文本行。</param>
        /// <param name="header">用于返回此文本行头部的格式字符串。</param>
        /// <param name="tail">用于返回此文本行尾部的正文字符串。</param>
        /// <param name="level">用于返回此文本行的层级，从1开始。当此层级为1时，文本行形如：！XXX。</param>
        /// <param name="startWithCommentMark">是否识别被分号注释掉的树型文字表。</param>
        public static bool IsTreeListLine(string line, out string header, out string tail, out int level, bool startWithCommentMark = false)
        {
            if (string.IsNullOrEmpty(line))
            {
                header = "";
                tail = "";
                level = 0;//0表示不是树型列表文本行。
                return false;
            }

            if (startWithCommentMark)
            {
                var regComment = new Regex(@"^[;；][ 　\t]{0,}");
                var cMatch = regComment.Match(line);
                if (cMatch.Success)
                {
                    line = line.Substring(cMatch.Length);
                }
            }

            if (CustomMarkdownSupport.IsImageLinkLine(line))
            {
                header = "";
                tail = "";
                level = 0;        //0表示不是树型列表文本行。
                return false;
            }

            Regex rgx = new Regex(@"^[!！][│　]{0,}[├└]{0,}(?!([ 　\t]*\{))");
            var match = rgx.Match(line);
            if (match.Success)
            {
                header = match.Value;
                tail = line.Substring(match.Length);
                level = header.Length;
                return true;
            }
            else
            {
                header = tail = "";
                level = 0;//0表示不是树型列表文本行。
                return false;
            }
        }

        public static Tuple<string, int> 是否树型文字表行(string line)
        {
            if (string.IsNullOrWhiteSpace(line)) return null;
            var startWithCommentMark = line.StartsWith(";") || line.StartsWith("；");

            if (IsTreeListLine(line, out string header, out string tail, out int level, startWithCommentMark))
            {
                return new Tuple<string, int>(tail, level);
            }

            return null;
        }

        private DocumentLine CurrentDocumentLine()
        {
            return this.Document.GetLineByOffset(this.SelectionStart);
        }

        private string CurrentLine()
        {
            int preReturnIndex = this.Text.LastIndexOf("\r\n", this.SelectionStart);
            int nextReturnIndex = this.Text.IndexOf("\r\n", this.SelectionStart);

            if (preReturnIndex < 0) preReturnIndex = -2;//后面会加2（\r\n's Length）
            if (nextReturnIndex < 0) nextReturnIndex = this.Text.Length;

            var length = nextReturnIndex - preReturnIndex - 2;
            if (length < 0) length = 0;

            return this.Text.Substring(preReturnIndex + 2, length);
        }

        private DocumentLine PreviewDocumentLine()
        {
            var curLine = CurrentDocumentLine();
            if (curLine == null || curLine.Offset <= 0) return null;

            return this.Document.GetLineByOffset(curLine.Offset - 2);//-\r\n's Length
        }

        private string PreviewLine()
        {
            int preReturnIndex = this.Text.LastIndexOf("\r\n", this.SelectionStart);
            if (preReturnIndex <= 0) return "";

            int prepreReturnIndex = this.Text.LastIndexOf("\r\n", preReturnIndex - 1);
            if (prepreReturnIndex < 0) return this.Text.Substring(0, preReturnIndex);

            return this.Text.Substring(prepreReturnIndex + 2, preReturnIndex - prepreReturnIndex - 2);
        }

        private DocumentLine NextDocumentLine()
        {
            var curLine = CurrentDocumentLine();

            //当插入点在最后一个字符时，TextLength==lastLine.EndOffset
            if (curLine == null || curLine.EndOffset >= this.Document.TextLength) return null;

            return this.Document.GetLineByOffset(curLine.EndOffset + 2);//+\r\n's Length
        }

        private string NextLine()
        {
            int nextReturnIndex = this.Text.IndexOf("\r\n", this.SelectionStart);
            if (nextReturnIndex < 0) return "";

            int nextnextReturnIndex = this.Text.IndexOf("\r\n", nextReturnIndex + 2);

            if (nextnextReturnIndex < 0) return this.Text.Substring(nextReturnIndex + 2);

            return this.Text.Substring(nextReturnIndex + 2, nextnextReturnIndex - nextReturnIndex - 2);
        }

        /// <summary>
        /// 根据当前行的上一行决定可以移动的层级，并进行整体格式化。
        /// 左移可连续多次移动，不需要考虑上一行的层级，但需要考虑上一行的格式化。
        /// 左移前跟随的所有下级均须同步左移。
        /// </summary>
        private void MoveTreeListLineLevelLeft(DocumentLine curLine, string header, string tail, int level, bool autoAggregate = false)
        {
            if (level <= 1) return;//1级标题不能再左移了。

            //左移时，所有级别高于当前行的、跟随在当前行之后的都顺序左移一级。

            //基本思路：本行左移时，先自动生成本行的基准头部。
            //          由于本行左移时，可能对前面所有行产生影响，所以要对前面的行重新计算格式字符；
            //          本行左移时，可能对后面所有行产生影响，所以还需要对后面的行重新计算格式字符。
            //          →所以不如寻找头、尾，然后依次重新写格式字符。
            //层级标准：只有一个！号的算1级，[！│　├]这四个字符（其中一个是全角空格）每有一个加1级。

            List<TreeListTextLine> previewLines = new List<TreeListTextLine>();

            var preLine = curLine.PreviousLine;
            while (preLine != null)
            {
                var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
                string preHeader, preTail; int preLevel;
                if (IsTreeListLine(preLineText, out preHeader, out preTail, out preLevel))
                {
                    previewLines.Add(new TreeListTextLine()
                    {
                        Line = preLine,
                        OldText = preLineText,
                        NewText = "",
                        //HeaderTextArray = preHeader.ToCharArray(),
                        TailText = preTail,
                        OldLevel = preLevel,
                        NewLevel = preLevel,   //之前的行层级不变
                    });

                    preLine = preLine.PreviousLine;
                }
                else break;
            }

            List<TreeListTextLine> allLines = new List<TreeListTextLine>();

            for (int i = previewLines.Count - 1; i >= 0; i--)
            {
                allLines.Add(previewLines[i]);
            }

            List<TreeListTextLine> moveLines = new List<TreeListTextLine>();

            moveLines.Add(new TreeListTextLine()
            {
                Line = curLine,
                //HeaderTextArray = header.ToCharArray(),
                NewText = "",
                OldText = this.Document.GetText(curLine.Offset, curLine.Length),
                TailText = tail,
                OldLevel = level,
                NewLevel = level - 1,      //当前行层级降一级
            });
            allLines.Add(moveLines[0]);

            var nextLine = curLine.NextLine;
            while (nextLine != null)
            {
                string oldNextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
                string nextHeader, nextTail; int nextLevel;
                if (IsTreeListLine(oldNextLineText, out nextHeader, out nextTail, out nextLevel))
                {
                    if (nextLevel > level)
                    {
                        var nextTreeListTextLine = new TreeListTextLine()
                        {
                            Line = nextLine,
                            //HeaderTextArray = nextHeader.ToCharArray(),
                            NewText = "",
                            OldText = oldNextLineText,
                            TailText = nextTail,
                            OldLevel = nextLevel,
                            NewLevel = nextLevel - 1,             //层级高于当前行的也降一级
                        };
                        moveLines.Add(nextTreeListTextLine);
                        allLines.Add(nextTreeListTextLine);      //无论层级都应加入此列表
                    }
                    else break;
                }
                else break;

                nextLine = nextLine.NextLine;
            }

            while (nextLine != null)
            {
                string oldNextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
                string nextHeader, nextTail; int nextLevel;
                if (IsTreeListLine(oldNextLineText, out nextHeader, out nextTail, out nextLevel))
                {
                    var nextTreeListTextLine = new TreeListTextLine()
                    {
                        Line = nextLine,
                        //HeaderTextArray = nextHeader.ToCharArray(),
                        NewText = "",
                        OldText = oldNextLineText,
                        TailText = nextTail,
                        OldLevel = nextLevel,
                        NewLevel = nextLevel,             //当碰到一个层级与当前行移位前层级相同的行后，之后所有行都不再降级。
                    };
                    allLines.Add(nextTreeListTextLine);
                }
                else break;
                nextLine = nextLine.NextLine;
            }

            //重新格式化。
            FormatAllTreeLines(allLines, true, autoAggregate);
        }

        /// <summary>
        /// 根据当前行的上一行决定可以移动的层级，并进行整体格式化。
        /// 右移最多移动到上一行的下一级。
        /// 右移前跟随的所有下级同步右移一级。
        /// </summary>
        private void MoveTreeListLineLevelRight(DocumentLine curLine, string header, string tail, int level, bool autoAggregate = false)
        {
            if (curLine == null) return;

            //右移时最多移动到上一行的下一级。
            //如果上一行不是树型列表项，禁止；如果上一行是树型列表项，最多移动到上一行的更高1级。

            var preLine = curLine.PreviousLine;
            if (preLine == null) return;

            var pText = this.Document.GetText(preLine.Offset, preLine.Length);
            string pHeader, pTail; int pLevel;
            if (IsTreeListLine(pText, out pHeader, out pTail, out pLevel))
            {
                if (level > pLevel) return;
            }
            else return;

            List<TreeListTextLine> allLines = new List<TreeListTextLine>();

            List<TreeListTextLine> previewLines = new List<TreeListTextLine>();

            while (preLine != null)
            {
                var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
                string preHeader, preTail; int preLevel;
                if (IsTreeListLine(preLineText, out preHeader, out preTail, out preLevel))
                {
                    previewLines.Add(new TreeListTextLine()
                    {
                        Line = preLine,
                        OldText = preLineText,
                        NewText = "",
                        //HeaderTextArray = preHeader.ToCharArray(),
                        TailText = preTail,
                        OldLevel = preLevel,
                        NewLevel = preLevel,   //之前的行层级不变
                    });

                    preLine = preLine.PreviousLine;
                }
                else break;
            }

            for (int i = previewLines.Count - 1; i >= 0; i--)
            {
                allLines.Add(previewLines[i]);
            }

            List<TreeListTextLine> moveLines = new List<TreeListTextLine>();

            moveLines.Add(new TreeListTextLine()
            {
                Line = curLine,
                //HeaderTextArray = header.ToCharArray(),
                NewText = "",
                OldText = this.Document.GetText(curLine.Offset, curLine.Length),
                TailText = tail,
                OldLevel = level,
                NewLevel = level + 1,      //当前行层级降一级
            });
            allLines.Add(moveLines[0]);

            var nextLine = curLine.NextLine;
            while (nextLine != null)
            {
                string oldNextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
                string nextHeader, nextTail; int nextLevel;
                if (IsTreeListLine(oldNextLineText, out nextHeader, out nextTail, out nextLevel))
                {
                    if (nextLevel > level)
                    {
                        var nextTreeListTextLine = new TreeListTextLine()
                        {
                            Line = nextLine,
                            //HeaderTextArray = nextHeader.ToCharArray(),
                            NewText = "",
                            OldText = oldNextLineText,
                            TailText = nextTail,
                            OldLevel = nextLevel,
                            NewLevel = nextLevel + 1,             //层级高于当前行的也升一级
                        };
                        moveLines.Add(nextTreeListTextLine);
                        allLines.Add(nextTreeListTextLine);      //无论层级都应加入此列表
                    }
                    else break;
                }
                else break;

                nextLine = nextLine.NextLine;
            }

            while (nextLine != null)
            {
                string oldNextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
                string nextHeader, nextTail; int nextLevel;
                if (IsTreeListLine(oldNextLineText, out nextHeader, out nextTail, out nextLevel))
                {
                    var nextTreeListTextLine = new TreeListTextLine()
                    {
                        Line = nextLine,
                        //HeaderTextArray = nextHeader.ToCharArray(),
                        NewText = "",
                        OldText = oldNextLineText,
                        TailText = nextTail,
                        OldLevel = nextLevel,
                        NewLevel = nextLevel,             //当碰到一个层级与当前行移位前层级相同的行后，之后所有行都不再升级。
                    };
                    allLines.Add(nextTreeListTextLine);
                }
                else break;
                nextLine = nextLine.NextLine;
            }

            //重新格式化。
            FormatAllTreeLines(allLines, false, autoAggregate);
        }

        private void FormatAllTreeLines(List<TreeListTextLine> allLines, bool? selectLeft = false, bool autoAggregate = false)
        {
            if (allLines == null || allLines.Count <= 0) return;

            foreach (var tLine in allLines)
            {
                tLine.HeaderTextArray = BuildTreeLineBaseHeader(tLine.NewLevel).ToCharArray();
            }

            int basePreLevel = 0;

            for (int i = 0; i < allLines.Count; i++)
            {
                var tLine = allLines[i];
                if (tLine.OldLevel > basePreLevel + 1)
                {
                    tLine.NewLevel = basePreLevel + 1;
                }
                basePreLevel = tLine.NewLevel;
            }

            for (int i = 0; i < allLines.Count; i++)
            {
                var tLine = allLines[i];

                //格式化冒号开头的注释
                if (tLine.TailText.StartsWith(":"))
                {
                    tLine.TailText = "：" + tLine.TailText.Substring(1);
                }

                var newHeaderText = BuildTreeLineBaseHeader(tLine.NewLevel);
                tLine.NewText = newHeaderText + tLine.TailText;
                tLine.HeaderTextArray = newHeaderText.ToCharArray();

                //根据情况刷新竖线。
                if (tLine.NewLevel <= 1) continue;

                for (int j = i - 1; j >= 0; j--)
                {
                    var aPreLine = allLines[j];
                    if (aPreLine.NewLevel < tLine.NewLevel) break;
                    if (aPreLine.NewLevel == tLine.NewLevel)
                    {
                        aPreLine.HeaderTextArray[tLine.NewLevel - 1] = '├';
                        break;
                    }

                    if (aPreLine.NewLevel > tLine.NewLevel)
                    {
                        aPreLine.HeaderTextArray[tLine.NewLevel - 1] = '│';
                    }
                }
            }

            // 汇总下级数据
            if (autoAggregate)
            {
                TreeListTextLine.Aggregate(ref allLines);
            }

            StringBuilder sb = new StringBuilder();
            foreach (var line in allLines)
            {
                sb.Append(new string(line.HeaderTextArray) + line.TailText);
                sb.Append("\r\n");
            }

            this.BeginChange();

            var oldSelectionStart = this.SelectionStart;
            var oldSelectionLength = this.SelectionLength;

            var newText = sb.ToString();
            if (newText.EndsWith("\r\n"))
            {
                newText = newText.Substring(0, newText.Length - 2);
            }

            this.Document.Replace(allLines[0].Line.Offset, allLines[allLines.Count - 1].Line.EndOffset - allLines[0].Line.Offset, newText);

            if (selectLeft.HasValue)
            {
                if (selectLeft.Value)
                {
                    if (oldSelectionStart > 0)
                    {
                        this.Select(oldSelectionStart - 1, oldSelectionLength);
                    }
                    else { this.Select(0, 0); }
                }
                else
                {
                    if (oldSelectionLength < this.Document.TextLength - 1)
                    {
                        this.Select(oldSelectionStart + 1, oldSelectionLength);
                    }
                }
            }

            this.EndChange();
        }

        private string BuildTreeLineBaseHeader(int level)
        {
            if (level <= 0) return "";
            if (level == 1) return "！";
            if (level == 2) return "！└";

            return $"！{new string('　', level - 2)}└";
        }

        private void InsertNewLineAndFormat(bool insertAtPreview = false)
        {
            var curLine = CurrentDocumentLine();
            var preLine = PreviewDocumentLine();
            var nextLine = NextDocumentLine();

            var curLineText = curLine == null ? "" : this.Document.GetText(curLine.Offset, curLine.Length);
            var preLineText = preLine == null ? "" : this.Document.GetText(preLine.Offset, preLine.Length);
            var nextLineText = nextLine == null ? "" : this.Document.GetText(nextLine.Offset, nextLine.Length);

            //MessageBox.Show($"P行：{preLineText}\r\nC行：{curLineText}\r\nN行：{nextLineText}");

            string headerText, tailText;
            int level;
            if (IsTreeListLine(curLineText, out headerText, out tailText, out level))
            {
                if (this.SelectionStart == curLine.Offset && this.SelectionLength == 0)
                {
                    this.BeginChange();
                    this.Document.Insert(curLine.Offset, "\r\n");
                    this.EndChange();

                    this.Select(curLine.Offset + 2, 0);
                    return;
                }

                this.BeginChange();

                int startReplaceOffset = this.SelectionStart;
                int tailStartOffset = curLine.Offset + headerText.Length;
                int replaceLength = curLine.EndOffset - this.SelectionStart;
                if (startReplaceOffset < tailStartOffset)
                {
                    startReplaceOffset = tailStartOffset;
                    replaceLength = curLine.EndOffset - startReplaceOffset;
                }
                var curTail = this.Document.GetText(startReplaceOffset, replaceLength);
                this.Select(this.SelectionStart, 0);

                var mark = "└";
                string nextHeaderText, nextTailText; int nextLevel;
                if (IsTreeListLine(nextLineText, out nextHeaderText, out nextTailText, out nextLevel) && nextLevel == level)
                {
                    mark = "├";
                }

                if (NextBrotherLineExist(curLine, level))
                {
                    mark = "├";
                }
                else
                {
                    mark = "└";
                }

                if (level == 1) mark = "！";

                if (curLine.TotalLength == curLine.Length)//没有定界符（\r\n）最后一行
                {
                    this.Document.Insert(curLine.EndOffset, "\r\n" + headerText.Substring(0, headerText.Length - 1) + $"{mark}{curTail}\r\n");
                }
                else
                {
                    this.Document.Insert(curLine.EndOffset + 2, headerText.Substring(0, headerText.Length - 1) + $"{mark}{curTail}\r\n");
                }

                this.Select(curLine.EndOffset + 2 + headerText.Length, 0);

                if (replaceLength > 0)//这个替换必须放到后面进行
                {
                    this.Document.Replace(startReplaceOffset, replaceLength, "");
                }

                FormatPreviewBrotherLines(this.Document.GetLineByNumber(curLine.LineNumber + 1), level);
                this.EndChange();
            }
        }

        private bool NextBrotherLineExist(DocumentLine curLine, int level)
        {
            if (level <= 0) return false;
            if (curLine == null || curLine.LineNumber >= this.Document.LineCount) return false;

            var nextLine = this.Document.GetLineByNumber(curLine.LineNumber + 1);
            while (nextLine != null)
            {
                string nextHeader, nextTail; int nextLevel;
                if (IsTreeListLine(this.Document.GetText(nextLine.Offset, nextLine.Length), out nextHeader, out nextTail, out nextLevel))
                {
                    if (nextLevel < level) return false;//截断
                    if (nextLevel == level) return true;
                    else
                    {
                        if (nextLine.LineNumber == this.Document.LineCount) return false;
                        nextLine = this.Document.GetLineByNumber(nextLine.LineNumber + 1);
                        continue;
                    }
                }
                else
                {
                    if (nextLine.LineNumber == this.Document.LineCount) return false;
                    nextLine = this.Document.GetLineByNumber(nextLine.LineNumber + 1);
                    continue;
                }
            }

            return false;
        }

        private void FormatPreviewBrotherLines(DocumentLine curLine, int level)
        {
            if (level == 1) return;//level==1时，只有惊叹号开头，其它不论。

            if (curLine == null || curLine.LineNumber <= 1) return;
            DocumentLine preLine = null;
            preLine = this.Document.GetLineByNumber(curLine.LineNumber - 1);

            while (preLine != null)
            {
                var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
                string headerText, tailText; int preLevel;
                if (IsTreeListLine(preLineText, out headerText, out tailText, out preLevel) == false) return;
                if (preLevel != level) break;

                if (preLineText[level - 1] != '├')
                {
                    this.Document.Replace(preLine.Offset + preLevel - 1, 1, "├");
                }

                if (preLine.LineNumber <= 1) break;
                preLine = this.Document.GetLineByNumber(preLine.LineNumber - 1);
            }
        }

        #endregion

        /// <summary>
        /// 跳转到下一个输入区域。这些输入区域包括：成对的小括弧之间，成对的大括弧之间，成对的中括弧之间等等。
        /// </summary>
        /// <param name="e"></param>
        private bool JumpOverMarks(ref KeyEventArgs e, bool isShift)
        {
            if (SelectionLength != 0) return false;
            //只有当插入点正好在某些特殊符号（及符号组合）前面时才起作用，用来跳过这些个特殊符号组合。

            try
            {
                var regexText = "(</?[a-zA-Z]{1,}( [^><]*)?>)|(\\*\\*)|(\\[\\=)|(\\=\\])|(__)|(\\{\\{)|(\\}\\})|([_\\*“”‘’'\"`<>《》〈〉\\(\\)\\[\\]\\{\\}（）〔〕〈〉「」『』〖〗【】［］｛｝＂＇°])";

                Regex regex = GetRegEx(regexText, isShift);
                int start = regex.Options.HasFlag(RegexOptions.RightToLeft) ? SelectionStart : SelectionStart + SelectionLength;
                Match match = regex.Match(Text, start);

                if (!match.Success)  // start again from beginning or end
                {
                    if (regex.Options.HasFlag(RegexOptions.RightToLeft))
                        match = regex.Match(Text, Text.Length);
                    else
                        match = regex.Match(Text, 0);
                }

                if (match.Success)
                {
                    if (isShift)
                    {
                        if (match.Index + match.Length != SelectionStart) return false;

                        var destSel = match.Index;
                        if (destSel >= 0)
                        {
                            Select(destSel, 0);
                            TextLocation loc = Document.GetLocation(match.Index);
                            //ScrollTo(loc.Line, loc.Column);  // 容易导致不必要的跳动

                            if (Globals.MainWindow.AutoSwitchInputMethod)
                            {
                                Globals.SwitchInputMethod(true);
                            }
                            e.Handled = true;
                            return true;
                        }
                    }
                    else
                    {
                        if (match.Index != SelectionStart + SelectionLength) return false;

                        var destSel = match.Index + match.Length;
                        if (destSel <= Document.TextLength)
                        {
                            var destLine = this.Document.GetLineByOffset(destSel);
                            var destRightText = this.Document.GetText(destSel, destLine.EndOffset - destSel);
                            if (destRightText == "  }") destSel += 1;

                            if (match.Value == "`" && string.IsNullOrEmpty(destRightText))
                            {
                                this.Document.Insert(destSel, " ", AnchorMovementType.Default);  // 自动添加空格
                                Select(destSel + 1, 0);
                            }
                            else
                            {
                                Select(destSel, 0);
                            }

                            TextLocation loc = Document.GetLocation(match.Index);
                            //ScrollTo(loc.Line, loc.Column);  // 容易导致不必要的跳动

                            if (Globals.MainWindow.AutoSwitchInputMethod)
                            {
                                Globals.SwitchInputMethod(true);
                            }
                            e.Handled = true;
                            return true;
                        }
                    }

                    return false;
                }

                return false;
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return false;
            }
        }

        /// <summary>
        /// 在成对匹配的标记之间跳转。
        /// 为防止误操作，此功能禁止跨行选取时触发。
        /// 例如：当前选择区在一对双引号中（形如“xxIxxxx”），此时，按下 Tab 键应将插入点跳转到后面的一对双引号“yyy”中，且应自动选中双引号中全部文本。这样可以快速跳转输入。
        /// </summary>
        public bool JumpBetweenMarks(ref KeyEventArgs e, bool isPreview = false)
        {
            try
            {
                var seltxt = seld_txt;
                if (seltxt.Contains("\r\n")) return false;

                var textToFind = @"((?<=`).*?(?=`))|((?<=〈).*?(?=〉))|((?<=《).*?(?=》))|((?<=【).*?(?=】))|((?<=〖).*?(?=〗))|((?<=｛).*?(?=｝))|((?<=［).*?(?=］))|((?<=\().*?(?=\)))|((?<=').*?(?='))|((?<="").*?(?=""))|((?<=‘).*?(?=’))|((?<=“).*?(?=”))|((?<=\[\=).*?(?=\=\]))|((?<=（).*?(?=）))";
                Regex regex = GetRegEx(textToFind, isPreview);
                int start = regex.Options.HasFlag(RegexOptions.RightToLeft) ? this.SelectionStart : this.SelectionStart + this.SelectionLength;
                Match match = regex.Match(this.Text, start);

                if (!match.Success)  // start again from beginning or end
                {
                    if (regex.Options.HasFlag(RegexOptions.RightToLeft))
                        match = regex.Match(this.Text, this.Text.Length);
                    else
                        match = regex.Match(this.Text, 0);
                }

                if (match.Success)
                {
                    var destSel = match.Index;
                    if (destSel < this.Document.TextLength)
                    {
                        this.Select(destSel, match.Length);
                        e.Handled = true;
                    }
                    TextLocation loc = this.Document.GetLocation(match.Index);
                    // this.ScrollTo(loc.Line, loc.Column);  // 容易导致不必要的跳动
                    this.TextArea.Caret.BringCaretToView();
                }

                return match.Success;
            }
            catch
            {
                //LMessageBox.Show(ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return false;
            }
        }

        /// <summary>
        /// 自动完成窗口目前是否可见，是否正在进行提示。
        /// </summary>
        private bool IsCompleting
        {
            get
            {
                if (this.CompletionWindow != null && this.CompletionWindow.Visibility == Visibility.Visible)
                    return true;
                else return false;

            }
        }

        /// <summary>
        /// 切换目标文本的任务列表状态标记。
        /// </summary>
        /// <param name="backOrder">决定正序还是逆序。</param>
        /// <param name="isShift">决定是作用于本行还是作用于本行所在的任务列表项。false时仅作用于本行文本。</param>
        public void SwitchTaskListItemOrDateTimeLineState(bool backOrder, bool isShift)
        {
            int startOffset = this.SelectionStart;
            int selectionLength = this.SelectionLength;
            int endOffset = this.SelectionStart + this.SelectionLength;

            var startLine = this.Document.GetLineByOffset(startOffset);
            var endLine = this.Document.GetLineByOffset(endOffset);

            var startLineText = this.Document.GetText(startLine.Offset, startLine.Length);

            if (startLineText.StartsWith("    ") || startLineText.StartsWith("\t"))
            {
                LMessageBox.Show("插入点所在的这行文本属于一个代码块，不能执行此命令！", Globals.AppName,
                     MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (isShift)
            {
                if (CustomMarkdownSupport.IsTaskLine(startLineText) == false && startLine.LineNumber > 1)
                {
                    //如果当前行不是任务列表，则向文件头部倒找。
                    DocumentLine destLine = null;
                    destLine = this.Document.GetLineByNumber(startLine.LineNumber - 1);
                    while (destLine != null && destLine.LineNumber > 1)
                    {
                        var destLineText = this.Document.GetText(destLine.Offset, destLine.Length);
                        if (CustomMarkdownSupport.IsTaskLine(destLineText))
                        {
                            break;
                        }
                        destLine = this.Document.GetLineByNumber(destLine.LineNumber - 1);
                    }

                    if (destLine != null)
                    {
                        this.Select(destLine.Offset, destLine.Length);
                        SwitchTaskListItemOrDateTimeLineState(backOrder, false);
                        // this.Select(startLine.Offset, selectionLength);
                        return;
                    }
                }//如果本行就是任务列表，则直接改动本行即可，继续。
            }

            CustomMarkdownSupport.TaskListItemState state = GetTaskListItemState(startLineText);

            //只改第一行，避免无谓的错误，也可降低开发难度。
            //CustomMarkdownSupport.TaskListItemState destState;
            string newHeader;
            string oldHeader;
            if (backOrder)
            {
                switch (state)
                {
                    case CustomMarkdownSupport.TaskListItemState.Finished:
                        {
                            oldHeader = "[+]";
                            newHeader = "[%] ";
                            break;
                        }
                    case CustomMarkdownSupport.TaskListItemState.Precessing:
                        {
                            oldHeader = "[%]";
                            //destState = CustomMarkdownSupport.TaskListItemState.Finished;
                            newHeader = "[-] ";
                            break;
                        }
                    case CustomMarkdownSupport.TaskListItemState.UnStart:
                        {
                            oldHeader = "[-]";
                            //destState = CustomMarkdownSupport.TaskListItemState.Precessing;
                            newHeader = "[#] ";
                            break;
                        }
                    case CustomMarkdownSupport.TaskListItemState.Aborted:
                        {
                            oldHeader = "[#]";
                            //destState = CustomMarkdownSupport.NotTaskListItem
                            newHeader = "[+] ";
                            break;
                        }
                    default:
                        {
                            oldHeader = "";
                            //destState = CustomMarkdownSupport.TaskListItemState.UnStart;
                            newHeader = "[#] ";
                            break;
                        }
                }
            }
            else
            {
                switch (state)
                {
                    case CustomMarkdownSupport.TaskListItemState.Finished:
                        {
                            oldHeader = "[+]";
                            //destState = CustomMarkdownSupport.TodoListItemState.Abort;
                            newHeader = "[#] ";
                            break;
                        }
                    case CustomMarkdownSupport.TaskListItemState.Precessing:
                        {
                            oldHeader = "[%]";
                            //destState = CustomMarkdownSupport.TodoListItemState.Finished;
                            newHeader = "[+] ";
                            break;
                        }
                    case CustomMarkdownSupport.TaskListItemState.UnStart:
                        {
                            oldHeader = "[-]";
                            //destState = CustomMarkdownSupport.TodoListItemState.Precessing;
                            newHeader = "[%] ";
                            break;
                        }
                    case CustomMarkdownSupport.TaskListItemState.Aborted:
                        {
                            oldHeader = "[#]";
                            //destState = CustomMarkdownSupport.NotTodoListItem
                            newHeader = "[-] ";
                            break;
                        }
                    default:
                        {
                            oldHeader = "";
                            //destState = CustomMarkdownSupport.TodoListItemState.UnStart;
                            newHeader = "[-] ";
                            break;
                        }
                }
            }

            if (CustomMarkdownSupport.IsDateLine(startLineText))
            {
                if (oldHeader == "")
                {
                    var rightIndex = startLineText.IndexOf("]");
                    if (rightIndex >= 0)
                    {
                        startLineText = startLineText.Substring(0, rightIndex + 1) + newHeader + " " + startLineText.Substring(rightIndex + 1).TrimStart(new char[] { ' ' });
                        this.Select(startLine.Offset, startLine.Length);
                        this.SelectedText = startLineText;
                        var indexTmp = startLineText.IndexOf("]");
                        if (indexTmp >= 0)
                        {
                            this.Select(startLine.Offset + indexTmp + newHeader.Length + 1, 0);
                        }
                        else
                        {
                            this.Select(startLine.Offset, 0);
                        }
                        return;
                    }
                }
                else
                {
                    var indexOfOldHeader = startLineText.IndexOf(oldHeader);
                    if (indexOfOldHeader >= 0)
                    {
                        startLineText = startLineText.Substring(0, indexOfOldHeader) + newHeader + startLineText.Substring(indexOfOldHeader + oldHeader.Length).TrimStart(new char[] { ' ' });

                        this.Select(startLine.Offset, startLine.Length);
                        this.SelectedText = startLineText;
                        var indexTmp = startLineText.IndexOf("]");
                        if (indexTmp >= 0)
                        {
                            this.Select(startLine.Offset + indexTmp + newHeader.Length + 1, 0);
                        }
                        else
                        {
                            this.Select(startLine.Offset, 0);
                        }
                        return;
                    }
                }
            }

            int index = startLineText.Length;
            for (int i = 0; i < startLineText.Length - 1; i++)
            {
                char c = startLineText[i];
                if (c != ' ' && c != '　' && c != '\t' && c != '[' && c != ']' && c != '+' && c != '-' && c != '%' && c != '#')
                {
                    index = i;
                    break;
                }
            }
            var tail = startLineText.Substring(index);
            this.Select(startLine.Offset, startLine.Length);
            this.SelectedText = newHeader + tail;
            this.Select(startLine.Offset + newHeader.Length, 0);
        }

        private CustomMarkdownSupport.TaskListItemState GetTaskListItemState(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return CustomMarkdownSupport.TaskListItemState.NotTaskListItem;

            var tmp = lineText.Replace(" ", "").Replace("\t", "").Replace("　", "");
            if (tmp.StartsWith("[-]")) return CustomMarkdownSupport.TaskListItemState.UnStart;
            if (tmp.StartsWith("[%]")) return CustomMarkdownSupport.TaskListItemState.Precessing;
            if (tmp.StartsWith("[+]")) return CustomMarkdownSupport.TaskListItemState.Finished;
            if (tmp.StartsWith("[#]")) return CustomMarkdownSupport.TaskListItemState.Aborted;

            if (CustomMarkdownSupport.IsDateLine(lineText))
            {
                if (tmp.IndexOf("[-]") >= 0) return CustomMarkdownSupport.TaskListItemState.UnStart;
                if (tmp.IndexOf("[%]") >= 0) return CustomMarkdownSupport.TaskListItemState.Precessing;
                if (tmp.IndexOf("[+]") >= 0) return CustomMarkdownSupport.TaskListItemState.Finished;
                if (tmp.IndexOf("[#]") >= 0) return CustomMarkdownSupport.TaskListItemState.Aborted;
            }

            return CustomMarkdownSupport.TaskListItemState.NotTaskListItem;
        }

        /// <summary>
        /// 在当前选定文本两侧加上倾斜标记文本。
        /// </summary>
        public void SwitchItalic()
        {
            if (this.SelectionLength == 0)
            {
                //this.Document.Insert(this.SelectionStart, "_");
                this.SelectedText = "__";
                this.Select(this.SelectionStart + 1, 0);
            }
            else
            {
                var lineEnd = this.Document.GetLineByOffset(this.SelectionStart).EndOffset;
                if (this.SelectionStart + this.SelectionLength > lineEnd) return;//不支持跨行

                if ((this.SelectedText.StartsWith("_") && this.SelectedText.EndsWith("_")) ||
                    (this.SelectedText.StartsWith("*") && this.SelectedText.EndsWith("*")))
                {
                    this.SelectedText = this.SelectedText.Substring(1, this.SelectedText.Length - 2);
                }
                else
                {
                    this.SelectedText = "*" + this.SelectedText + "*";
                }
            }
        }

        /// <summary>
        /// 在当前选定文本两侧加上或取消代码片段标记文本（``，即一对反引号）。
        /// </summary>
        public void SwitchCodeSnippet()
        {
            if (this.SelectionLength == 0)
            {
                this.SelectedText = "``";
                this.Select(this.SelectionStart + 1, 0);
            }
            else
            {
                var lineEnd = this.Document.GetLineByOffset(this.SelectionStart).EndOffset;
                if (this.SelectionStart + this.SelectionLength > lineEnd) return;//不支持跨行

                if (this.SelectedText.StartsWith("`") && this.SelectedText.EndsWith("`"))
                {
                    this.SelectedText = this.SelectedText.Substring(1, this.SelectedText.Length - 2);
                }
                else
                {
                    this.SelectedText = "`" + this.SelectedText + "`";
                }
            }
        }

        /// <summary>
        /// 在当前选定文本两侧加上加粗标记文本。
        /// </summary>
        public void SwitchBold()
        {
            //原本使用****语法，但易造成冲突，在引用块中导致高亮显示不正常
            if (this.SelectionLength == 0)
            {
                //this.Document.Insert(this.SelectionStart, "_");
                this.SelectedText = "____";
                this.Select(this.SelectionStart + 2, 0);
            }
            else
            {
                var lineEnd = this.Document.GetLineByOffset(this.SelectionStart).EndOffset;
                if (this.SelectionStart + this.SelectionLength > lineEnd) return;//不支持跨行


                if ((this.SelectedText.StartsWith("__") && this.SelectedText.EndsWith("__")) ||
                    (this.SelectedText.StartsWith("**") && this.SelectedText.EndsWith("**")))
                {
                    this.SelectedText = this.SelectedText.Substring(2, this.SelectedText.Length - 4);
                }
                else
                {
                    this.SelectedText = "__" + this.SelectedText + "__";
                }
            }
        }

        /// <summary>
        /// 折叠插入点所在的可折叠区域。
        /// </summary>
        public void FoldSelectedBlock()
        {
            if (Globals.MainWindow.SupportFolding == false) return;

            ICSharpCode.AvalonEdit.Folding.FoldingSection folding = null;

            foreach (var i in this.FoldingManager.AllFoldings)
            {
                if (i.StartOffset <= this.SelectionStart &&
                    i.EndOffset >= this.SelectionStart + this.SelectionLength)
                {
                    if (folding == null)
                    {
                        folding = i;
                        continue;
                    }
                    else
                    {
                        if (folding.StartOffset < i.StartOffset)
                        {
                            folding = i;
                            continue;
                        }
                    }
                }
            }

            if (folding != null)
            {
                folding.IsFolded = !folding.IsFolded;
            }
        }

        public bool JumpToPreviewCell()
        {
            var line = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            if (line == null) return false;

            var text = this.Document.GetText(line.Offset, line.Length);
            if (text.Contains("|") == false && text.Contains("｜") == false) return false;

            if (this.SelectionStart == line.Offset && this.SelectionLength == 0)
            {
                if (this.SelectionStart - 2 >= 0)
                {
                    this.Select(this.SelectionStart - 2, 0);//2是\r\n的长度。
                    return true;
                }
            }

            //先在text中查找前一个分割线
            int preIndex = -1;
            for (int i = this.SelectionStart - 1; i >= 0; i--)
            {
                char c = this.Text[i];
                if (c == '|' || c == '｜' || c == '\n' || c == '\r')
                {
                    preIndex = i;
                    break;
                }
            }

            //再找后一个分割线位置
            int nextIndex = -1;
            for (int i = this.SelectionStart; i < this.Text.Length; i++)
            {
                char c = this.Text[i];
                if (c == '|' || c == '｜' || c == '\r' || c == '\n')
                {
                    nextIndex = i;
                    break;
                }
            }

            if (nextIndex >= 0)
            {
                if (preIndex >= 0 && nextIndex > preIndex)
                {
                    if (Globals.MainWindow.SelectCellFirst &&
                        (this.SelectionStart != preIndex + 1 || this.SelectionLength != nextIndex - preIndex - 1))
                    {
                        this.Select(preIndex + 1, nextIndex - preIndex - 1);
                        return true;
                    }
                    else
                    {
                        //如果已选中当前单元格所有文本，继续找上一个单元格并选定。
                        var preIndex2 = -1;
                        for (int i = preIndex - 1; i >= 0; i--)
                        {
                            char c = this.Text[i];
                            if (c == '|' || c == '｜' || c == '\r' || c == '\n')
                            {
                                preIndex2 = i;
                                break;
                            }
                        }

                        if (preIndex2 < 0 && preIndex > 0)
                        {
                            this.Select(preIndex - 1, 0);
                            return true;
                        }

                        if (preIndex2 < preIndex)
                        {
                            this.Select(preIndex2 + 1, preIndex - preIndex2 - 1);
                            return true;
                        }

                        return false;
                    }
                }
                else
                {
                    this.Select(0, 0);//整个文档开头
                    return true;
                }
            }
            else
            {
                //如果就在整个文档的最后一个位置，此时肯定找不到nextIndex
                //如果已选中当前单元格所有文本，继续找上一个单元格并选定。
                var preIndex2 = -1;
                for (int i = preIndex - 1; i >= 0; i--)
                {
                    char c = this.Text[i];
                    if (c == '|' || c == '｜' || c == '\r' || c == '\n')
                    {
                        preIndex2 = i;
                        break;
                    }
                }

                if (preIndex2 < 0 && preIndex > 0)
                {
                    this.Select(preIndex - 1, 0);
                    return true;
                }

                if (preIndex2 < preIndex)
                {
                    this.Select(preIndex2 + 1, preIndex - preIndex2 - 1);
                    return true;
                }

                return false;
            }
        }

        /// <summary>
        /// 跳转到下一个单元格。
        /// </summary>
        public bool JumpToNextCell()
        {
            var line = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            if (line == null) return false;
            var text = this.Document.GetText(line.Offset, line.Length);
            if (text.Contains("|") == false && text.Contains("｜") == false) return false;

            if (this.SelectionStart == line.EndOffset && this.SelectionLength == 0)
            {
                if (this.SelectionStart + 2 < this.Text.Length)
                {
                    this.Select(this.SelectionStart + 2, 0);//2是\r\n的长度。
                }
                return true;
            }

            //先在text中查找前一个分割线
            int preIndex = -1;
            for (int i = this.SelectionStart + this.SelectionLength - 1; i >= 0; i--)
            {
                char c = this.Text[i];
                if (c == '|' || c == '｜' || c == '\n' || c == '\r')
                {
                    preIndex = i;
                    break;
                }
            }

            //再找后一个分割线位置
            int nextIndex = -1;
            for (int i = this.SelectionStart + this.SelectionLength; i < this.Text.Length; i++)
            {
                char c = this.Text[i];
                if (c == '|' || c == '｜' || c == '\r' || c == '\n')
                {
                    nextIndex = i;
                    break;
                }
            }

            if (nextIndex > preIndex)
            {
                //如果在首行
                if (line.LineNumber == 1 && preIndex < 0)
                {
                    if (this.SelectionStart != 0 || this.SelectionLength != nextIndex)
                    {
                        this.Select(0, nextIndex);
                    }
                    else
                    {
                        //如果已选中当前单元格所有文本，继续找下一个单元格并选定。
                        var nextIndex2 = -1;
                        for (int i = nextIndex + 1; i < this.Text.Length; i++)
                        {
                            char c = this.Text[i];
                            if (c == '|' || c == '｜' || c == '\r' || c == '\n')
                            {
                                nextIndex2 = i;
                                break;
                            }
                        }

                        if (nextIndex2 < 0)
                        {
                            this.Select(nextIndex + 1, 0);
                        }
                        else if (nextIndex2 > nextIndex)
                        {
                            this.Select(nextIndex + 1, nextIndex2 - nextIndex - 1);
                        }
                    }
                    return true;
                }

                if (Globals.MainWindow.SelectCellFirst &&
                    (this.SelectionStart != preIndex + 1 || this.SelectionLength != nextIndex - preIndex - 1))
                {
                    this.Select(preIndex + 1, nextIndex - preIndex - 1);
                }
                else
                {
                    //如果已选中当前单元格所有文本，继续找下一个单元格并选定。
                    var nextIndex2 = -1;
                    for (int i = nextIndex + 1; i < this.Text.Length; i++)
                    {
                        char c = this.Text[i];
                        if (c == '|' || c == '｜' || c == '\r' || c == '\n')
                        {
                            nextIndex2 = i;
                            break;
                        }
                    }

                    if (nextIndex2 < 0)
                    {
                        this.Select(nextIndex + 1, 0);
                    }
                    else if (nextIndex2 > nextIndex)
                    {
                        this.Select(nextIndex + 1, nextIndex2 - nextIndex - 1);
                    }
                }
                return true;
            }

            return false;
        }

        /// <summary>
        /// 跳转到下一个试题元素。
        /// </summary>
        /// <param name="isShift">Shift 键是否处于被按下状态。</param>
        private bool JumpToNextExamElement(bool isShift)
        {
            if (Globals.MainWindow.IsExamEnabled == false) return false;

            string textToFind = "＞＞[^＞]";
            Regex regex = GetRegEx(textToFind, isShift);
            int start;

            if (isShift)
            {
                var line = this.Document.GetLineByOffset(this.SelectionStart);
                if (line != null)
                {
                    var lineText = this.Document.GetText(line.Offset, line.Length);

                    //找一下，前一个【】在哪里
                    //if (lineText.StartsWith("　　试题＞＞"))
                    //{
                    var startIndex = this.SelectionStart + this.SelectionLength - line.Offset - 1;
                    if (startIndex >= 0)
                    {
                        var rightIndex = lineText.LastIndexOf('】', startIndex);
                        if (rightIndex >= 0)
                        {
                            var leftIndex = lineText.LastIndexOf('【', rightIndex);
                            if (rightIndex > leftIndex)
                            {
                                this.Select(line.Offset + leftIndex + 1, rightIndex - leftIndex - 1);
                                TextLocation loc = this.Document.GetLocation(line.Offset + leftIndex + 1);
                                //his.ScrollTo(loc.Line, loc.Column);  // 容易导致不必要的跳动
                                this.TextArea.Caret.BringCaretToView();
                                return true;
                            }
                        }
                    }
                    //}

                    if (lineText.Contains("＞＞"))
                    {
                        start = line.Offset;//跳过这行的开头向文件头部逆向查找，否则只能停留在本行（这样没用处）                        
                    }
                    else
                    {
                        start = regex.Options.HasFlag(RegexOptions.RightToLeft) ? this.SelectionStart : this.SelectionStart + this.SelectionLength;
                    }
                }
                else return false;
            }
            else
            {
                var line = this.Document.GetLineByOffset(this.SelectionStart);
                if (line != null)
                {
                    //正向时，不需要越过标记，所以这里不需要判断本行有无“＞＞”。

                    //找一下，下一个【】在哪里
                    var lineText = this.Document.GetText(line.Offset, line.Length);

                    var startIndex = this.SelectionStart - line.Offset;
                    if (startIndex >= 0)
                    {
                        var leftIndex = lineText.IndexOf('【', startIndex);
                        if (leftIndex >= 0)
                        {
                            var rightIndex = lineText.IndexOf('】', leftIndex);
                            if (rightIndex > leftIndex)
                            {
                                this.Select(line.Offset + leftIndex + 1, rightIndex - leftIndex - 1);
                                TextLocation loc = this.Document.GetLocation(line.Offset + leftIndex + 1);
                                //this.ScrollTo(loc.Line, loc.Column);   // 容易导致不必要的跳动
                                this.TextArea.Caret.BringCaretToView();
                                return true;
                            }
                        }
                    }

                    start = line.EndOffset;
                }
                else return false;
            }

            Match match = regex.Match(this.Text, start);

            if (!match.Success)  //重头开始或结束
            {
                if (regex.Options.HasFlag(RegexOptions.RightToLeft))
                    match = regex.Match(this.Text, this.Text.Length);
                else
                    match = regex.Match(this.Text, 0);
            }

            if (match.Success)
            {
                var destLine = this.Document.GetLineByOffset(match.Index + 2);
                int length;
                var destLineText = this.Document.GetText(destLine.Offset, destLine.Length);
                var firstIndex = match.Index - destLine.Offset + 2;
                var lastIndex = destLineText.LastIndexOf("＜＜");
                if (lastIndex >= firstIndex)
                {
                    length = lastIndex - firstIndex;
                }
                else
                {
                    length = destLine.EndOffset - match.Index - 2;
                }

                this.Select(match.Index + 2, length);
                TextLocation loc = this.Document.GetLocation(match.Index);
                // this.ScrollTo(loc.Line, loc.Column);  // 容易导致不必要的跳动
                this.TextArea.Caret.BringCaretToView();
            }

            return match.Success;
        }

        /// <summary>
        /// 根据选项构建正则表达式。
        /// </summary>
        /// <param name="textToFind">要查找的文本</param>
        /// <param name="leftToRight">正序还是逆序。</param>
        private Regex GetRegEx(string textToFind, bool leftToRight)
        {
            RegexOptions options = RegexOptions.None;
            if (leftToRight)
            {
                options |= RegexOptions.RightToLeft;
            }
            options |= RegexOptions.IgnoreCase;

            string pattern = textToFind; // Regex.Escape(textToFind);
            return new Regex(pattern, options);
        }

        /// <summary>
        /// 插入新行。
        /// </summary>
        private void InsertANewLine()
        {
            var curLine = this.Document.GetLineByOffset(this.SelectionStart);
            var text = this.Document.GetText(curLine.Offset, curLine.Length);

            if (text.Contains("|") == false && text.Contains("｜") == false)
            {
                this.Document.Insert(curLine.Offset, "\r\n");
                this.Select(curLine.Offset, 0);
            }
            else
            {
                //如果本行是表格行，仿制一行
                StringBuilder sb = new StringBuilder();

                for (int i = 0; i < text.Length; i++)
                {
                    char c = text[i];
                    if (c == '|')
                    {
                        sb.Append("|");
                    }
                    else if (c == '｜')
                    {
                        sb.Append("｜");
                    }
                    else if ((int)c <= 255)
                    {
                        sb.Append(" ");
                    }
                    else
                    {
                        sb.Append("  ");
                    }
                }

                this.Document.Insert(curLine.Offset, sb.ToString() + "\r\n");
                this.Select(curLine.Offset, 0);
            }
        }

        /// <summary>
        /// 附加新行。
        /// </summary>
        private void AppendANewLine()
        {
            var curLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            var text = this.Document.GetText(curLine.Offset, curLine.Length);

            var regSquare = new Regex(@"^[ 　]{0,3}\[[^\[\]]{0,}$");
            var matchSquare = regSquare.Match(text);
            if (matchSquare.Success)
            {
                this.Document.Insert(curLine.EndOffset, "\r\n\r\n]");
                this.Select(curLine.EndOffset + 2, 0);
                return;
            }

            var regRegion = new Regex(@"^[ 　]{0,3}\{[\-+]?[^\{\}]{0,}$");
            var matchRegion = regRegion.Match(text);
            if (matchRegion.Success)
            {
                this.Document.Insert(curLine.EndOffset, "\r\n\r\n}");
                this.Select(curLine.EndOffset + 2, 0);
                return;
            }

            var regBracketEnd = new Regex(@"^[^\{\}]*\{[^\{\}]{0,}$");
            var matchBracketEnd = regBracketEnd.Match(text);
            if (matchBracketEnd.Success)
            {
                this.Document.Insert(curLine.EndOffset, "\r\n    \r\n}");
                this.Select(curLine.EndOffset + 6, 0);
                return;
            }

            if (text.Contains("|") == false && text.Contains("｜") == false)
            {
                this.Document.Insert(curLine.EndOffset, "\r\n");
                this.Select(curLine.EndOffset + 2, 0);
            }
            else
            {
                //如果本行是表格行，仿制一行
                StringBuilder sb = new StringBuilder();

                for (int i = 0; i < text.Length; i++)
                {
                    char c = text[i];
                    if (c == '|')
                    {
                        sb.Append("|");
                    }
                    else if (c == '｜')
                    {
                        sb.Append("｜");
                    }
                    else if ((int)c <= 255)
                    {
                        sb.Append(" ");
                    }
                    else
                    {
                        sb.Append("  ");
                    }
                }

                this.Document.Insert(curLine.EndOffset, "\r\n" + sb.ToString());
                this.Select(curLine.EndOffset + 2, 0);
            }
        }

        /// <summary>
        /// 与后一行交换位置。
        /// </summary>
        internal void SwapWithNextLine()
        {
            var curStartLine = this.Document.GetLineByOffset(this.SelectionStart);
            var curEndLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            var nextLine = curEndLine.NextLine;

            if (nextLine != null)
            {
                this.BeginChange();
                var lineSplitter = "\n";
                if (this.Document.Text.Contains("\r\n"))
                {
                    lineSplitter = "\r\n";
                }
                var nextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
                this.Document.Remove(nextLine.Offset - lineSplitter.Length, nextLine.Length + lineSplitter.Length);//先删除下一行

                var curStartLineOffset = curStartLine.Offset;
                this.Document.Insert(curStartLineOffset, nextLineText + lineSplitter);
                this.EndChange();
                this.TextArea.Caret.BringCaretToView();
            }
        }

        /// <summary>
        /// 与前一行交换位置。
        /// </summary>
        internal void SwapWithPreviewLine()
        {
            var curStartLine = this.Document.GetLineByOffset(this.SelectionStart);
            var curEndLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            var preLine = curStartLine.PreviousLine;

            if (preLine != null)
            {
                this.BeginChange();
                var preStartLineOffset = preLine.Offset;
                var startOffset = this.SelectionStart - curStartLine.Offset;
                var selLength = this.SelectionLength;

                var lineSplitter = "\n";
                if (this.Document.Text.Contains("\r\n"))
                {
                    lineSplitter = "\r\n";
                }

                var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
                this.Document.Insert(curEndLine.EndOffset, lineSplitter);
                this.Document.Insert(curEndLine.EndOffset + lineSplitter.Length, preLineText);
                this.Document.Remove(preLine.Offset, preLine.Length + lineSplitter.Length);
                this.EndChange();

                this.Select(preStartLineOffset + startOffset, selLength);
                this.TextArea.Caret.BringCaretToView();
            }
        }

        /// <summary>
        /// 寻找前一个标题。
        /// </summary>
        public void FindPreviewHeader()
        {
            var curLine = this.Document.GetLineByOffset(this.SelectionStart);
            if (curLine == null) return;
            int i = curLine.LineNumber - 1;//当前行不算
            DocumentLine preHeaderLine = null;
            while (i > 0)
            {
                var preLine = this.Document.GetLineByNumber(i);
                if (preLine == null) break;

                var text = this.Document.GetText(preLine.Offset, preLine.Length);
                if (text == null) break;

                if (text.Length == 0)
                {
                    i--;
                    continue;
                }
                else
                {
                    Regex reg = new Regex(@"^[ 　#＃]{1,}");
                    if (reg.Match(text).Success)
                    {
                        preHeaderLine = preLine;
                        break;
                    }
                    else
                    {
                        i--;
                        continue;
                    }
                }
            }

            if (preHeaderLine != null)
            {
                this.Select(preHeaderLine.Offset, 0);
                this.ScrollTo(preHeaderLine.LineNumber, 0);
            }
            else
            {
                this.Select(0, 0);
                this.ScrollTo(0, 0);
            }
        }

        /// <summary>
        /// 寻找后一个标题。
        /// </summary>
        public void FindNextHeader()
        {
            var curLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            if (curLine == null) return;
            int i = curLine.LineNumber + 1;//当前行不算。
            DocumentLine nextHeaderLine = null;
            while (i < this.Document.LineCount)
            {
                var nextLine = this.Document.GetLineByNumber(i);
                if (nextLine == null) break;

                var text = this.Document.GetText(nextLine.Offset, nextLine.Length);
                if (text == null) break;

                if (text.Length == 0)
                {
                    i++;
                    continue;
                }
                else
                {
                    Regex reg = new Regex(@"^[ 　#＃]{1,}");
                    if (reg.Match(text).Success)
                    {
                        nextHeaderLine = nextLine;
                        break;
                    }
                    else
                    {
                        i++;
                        continue;
                    }
                }
            }

            if (nextHeaderLine != null)
            {
                this.Select(nextHeaderLine.Offset, 0);
                this.ScrollTo(nextHeaderLine.LineNumber, 0);
            }
            else
            {
                this.Select(this.Document.Lines[this.Document.Lines.Count - 1].EndOffset, 0);
                this.ScrollToLine(this.Document.Lines.Count - 1);
            }
        }

        /// <summary>
        /// 使当前文本行在“普通文本”与“无序列表项”之间切换。
        /// </summary>
        /// <param name="convertToListItem">作为无序列表项。</param>
        public void SwitchListMark(bool convertToListItem)
        {
            var startLine = this.Document.GetLineByOffset(this.SelectionStart);
            var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);

            this.BeginChange();

            for (int i = endLine.LineNumber - 1; i >= startLine.LineNumber - 1; i--)
            {
                var line = this.Document.Lines[i];
                var lineText = this.Document.GetText(line.Offset, line.Length);

                var lineContent = lineText.Trim(new char[] { ' ', '　', '\t' });
                if (string.IsNullOrWhiteSpace(lineContent)) continue;

                if (convertToListItem == false)
                {
                    //取消行首的无序列表标记。

                    Regex reg = new Regex(@"^[ 　]{0,3}[\+\-－\*][ 　\t]{0,}");
                    var match = reg.Match(lineText);
                    if (match != null && match.Success)
                    {
                        this.Document.Remove(line.Offset, match.Length);
                    }
                }
                else
                {
                    //在行首添加星号，作为无序列表
                    this.Document.Insert(line.Offset, "+ ");
                }
            }

            this.EndChange();
        }

        /// <summary>
        /// 在选定文本行前加“引用块标记文本（> ）”
        /// </summary>
        internal void AddBlockQuoterMarksToSelectedLines()
        {
            var startLine = this.Document.GetLineByOffset(this.SelectionStart);
            var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            var startReplaceOffset = startLine.Offset;
            var endReplaceOffset = endLine.EndOffset;

            StringBuilder sb = new StringBuilder();
            for (int i = startLine.LineNumber; i <= endLine.LineNumber; i++)
            {
                var line = this.Document.GetLineByNumber(i);
                var lineText = this.Document.GetText(line.Offset, line.Length);
                if (string.IsNullOrWhiteSpace(lineText))
                {
                    sb.Append("\r\n");
                    continue;
                }

                sb.Append("> ");
                sb.Append(lineText);
                sb.Append("\r\n");
            }
            this.Document.Replace(startReplaceOffset, endReplaceOffset - startReplaceOffset + 1, sb.ToString());
        }

        /// <summary>
        /// 在选定文本行前加“代码块标记（四个半角空格）”
        /// </summary>
        internal void AddCodeBlockMarksToSelectedLines()
        {
            try
            {
                var startLine = this.Document.GetLineByOffset(this.SelectionStart);
                var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
                var startReplaceOffset = startLine.Offset;
                var endReplaceOffset = endLine.EndOffset;
                var sNum = startLine.LineNumber;
                var eNum = endLine.LineNumber;

                StringBuilder sb = new StringBuilder();
                for (int i = startLine.LineNumber; i <= endLine.LineNumber; i++)
                {
                    var line = this.Document.GetLineByNumber(i);
                    var lineText = this.Document.GetText(line.Offset, line.Length);
                    if (string.IsNullOrWhiteSpace(lineText))
                    {
                        sb.Append("\r\n");
                        continue;
                    }

                    sb.Append("    ");
                    sb.Append(lineText);
                    sb.Append("\r\n");
                }
                this.Document.Replace(startReplaceOffset, endReplaceOffset - startReplaceOffset, sb.ToString());
                this.Select(Document.GetLineByNumber(sNum).Offset, Document.GetLineByNumber(eNum).EndOffset - startLine.Offset);
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 在选定文本行头部移除“代码块标记（四个半角空格）”
        /// </summary>
        internal void RemoveCodeBlockMarksToSelectedLines()
        {
            try
            {
                var startLine = this.Document.GetLineByOffset(this.SelectionStart);
                var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
                var startReplaceOffset = startLine.Offset;
                var endReplaceOffset = endLine.EndOffset;
                var sNum = startLine.LineNumber;
                var eNum = endLine.LineNumber;

                // 如果每行头部都有最少四个半角空格，则都删除四个；
                // 如果有些行只有不到四个，则都按最少的一行决定删除几个空格。
                int minSpaceCount = int.MaxValue;
                Regex regex = new Regex(@"^[ ]{0,}");
                List<string> lines = new List<string>();
                for (int i = startLine.LineNumber; i <= endLine.LineNumber; i++)
                {
                    var line = this.Document.GetLineByNumber(i);
                    var lineText = this.Document.GetText(line.Offset, line.Length);
                    lines.Add(lineText);
                    if (string.IsNullOrEmpty(lineText)) continue;
                    var match = regex.Match(lineText);
                    if (match.Success == false || match.Length <= 0)
                    {
                        minSpaceCount = 0;
                        break;
                    }

                    minSpaceCount = Math.Min(minSpaceCount, match.Length);
                }

                if (minSpaceCount <= 0) return;

                minSpaceCount = Math.Min(4, minSpaceCount);//每次最多删除四个。

                StringBuilder sb = new StringBuilder();
                foreach (var lt in lines)
                {
                    if (string.IsNullOrWhiteSpace(lt))
                    {
                        sb.Append("\r\n");
                        continue;
                    }

                    if (lt.StartsWith(new string(' ', minSpaceCount)))
                    {
                        sb.Append(lt.Substring(minSpaceCount));
                    }
                    else sb.Append(lt);

                    sb.Append("\r\n");
                }
                this.Document.Replace(startReplaceOffset, endReplaceOffset - startReplaceOffset, sb.ToString());
                this.Select(Document.GetLineByNumber(sNum).Offset, Document.GetLineByNumber(eNum).EndOffset - startLine.Offset);
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 使当前文本行在六级标题之间切换。
        /// </summary>
        /// <param name="newLevel">标题层级。</param>
        internal void SwitchTitleLevel(int newLevel)
        {
            var startLine = this.Document.GetLineByOffset(this.SelectionStart);
            var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            for (int i = startLine.LineNumber; i <= endLine.LineNumber; i++)
            {
                var line = this.Document.GetLineByNumber(i);
                var lineText = this.Document.GetText(line.Offset, line.Length);
                if (string.IsNullOrWhiteSpace(lineText))
                {
                    var header = GetTitleHeader(newLevel);
                    this.Document.Replace(line.Offset, 0, GetTitleHeader(newLevel));
                    this.Select(line.Offset + header.Length, 0);
                    return;
                }

                var oldSelOffset = this.SelectionStart;
                var oldSelLength = this.SelectionLength;

                Regex reg1 = new Regex(@"^[ 　]{4,}");
                var match1 = reg1.Match(lineText);
                if (match1 != null && match1.Success) return;//不考虑代码块

                Regex reg = new Regex(@"^[ 　#＃]{1,}");
                var match = reg.Match(lineText);
                if (match != null && match.Success)
                {
                    var level = GetTitleLevel(match.Value);
                    if (level == newLevel)
                    {
                        this.Document.Replace(line.Offset, match.Length, "");
                        this.Select(oldSelOffset - match.Length, oldSelLength);
                    }
                    else
                    {
                        var newHeader = GetTitleHeader(newLevel);
                        this.Document.Replace(line.Offset, match.Length, newHeader);
                        var newSelOffset = oldSelOffset + newHeader.Length - match.Length;
                        this.Select(newSelOffset, oldSelLength);
                    }
                }
                else
                {
                    var newHeader = GetTitleHeader(newLevel);
                    this.Document.Insert(line.Offset, GetTitleHeader(newLevel));
                    var newSelOffset = oldSelOffset + newHeader.Length - match.Length;
                    this.Select(newSelOffset, oldSelLength);
                }
            }
        }

        /// <summary>
        /// 取文本行的标题级别。
        /// </summary>
        /// <param name="lineText">文本行。</param>
        private int GetTitleLevel(string lineText)
        {
            if (string.IsNullOrWhiteSpace(lineText)) return 0;
            int level = 0;
            foreach (char c in lineText)
            {
                switch (c)
                {
                    case ' ':
                    case '　': continue;
                    case '＃':
                    case '#':
                        {
                            level++;
                            continue;
                        }
                    default: continue;
                }
            }

            return level;
        }

        /// <summary>
        /// 根据层级生成标题的头文本（#字符串）。。
        /// </summary>
        /// <param name="level">标题层级。</param>
        private string GetTitleHeader(int level)
        {
            switch (level)
            {
                case 1: return "# ";
                case 2: return "## ";
                case 3: return "### ";
                case 4: return "#### ";
                case 5: return "##### ";
                case 6: return "###### ";
                default: return "";
            }
        }

        /// <summary>
        /// 此方法是 this.Document.GetText(int offset, int length) 方法的别名。
        /// </summary>
        /// <param name="offset"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public string GetText(int offset, int length)
        {
            if (this.Document == null) return null;

            return this.Document.GetText(offset, length);
        }

        public string 取文本(int offset, int length)
        {
            return GetText(offset, length);
        }

        /// <summary>
        /// 此方法是 this.Document.Replace(int offset, int length, string text) 方法的别名。
        /// </summary>
        /// <param name="offset"></param>
        /// <param name="length"></param>
        /// <param name="text"></param>
        public void Replace(int offset, int length, string text)
        {
            if (this.Document == null) return;
            text = Utils.Text.FormatLineSplitter(text);
            this.Document.Replace(offset, length, text);
        }

        public void 替换(int offset, int length, string text)
        {
            Replace(offset, length, text);
        }

        /// <summary>
        /// 此方法是 this.Document.GetLineByNumber(int number) 方法的别名。
        /// </summary>
        /// <param name="number"></param>
        /// <returns></returns>
        public DocumentLine GetLineByNumber(int number)
        {
            if (this.Document == null) return null;

            return this.Document.GetLineByNumber(number);
        }

        public DocumentLine 据行号取行对象(int number)
        {
            return GetLineByNumber(number);
        }

        /// <summary>
        /// 此方法是 this.Document.GetLineByOffset(int offset) 方法的别名。
        /// </summary>
        /// <param name="offset"></param>
        /// <returns></returns>
        public DocumentLine GetLineByOffset(int offset)
        {
            if (this.Document == null) return null;

            return this.Document.GetLineByOffset(offset);
        }

        public DocumentLine 据位置取行对象(int offset)
        {
            return GetLineByOffset(offset);
        }

        /// <summary>
        /// 此方法是 this.Document.GEtOffset(int line, int column) 方法的别名。
        /// </summary>
        /// <param name="line"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        public int GetOffset(int line, int column)
        {
            if (this.Document == null) return -1;

            return this.Document.GetOffset(line, column);
        }

        public int 取字符位置(int line, int column)
        {
            return GetOffset(line, column);
        }

        /// <summary>
        /// this.Document.Remove(int offset, int length) 方法的别名。
        /// </summary>
        /// <param name="offset"></param>
        /// <param name="length"></param>
        public void Remove(int offset, int length)
        {
            if (this.Document == null) return;

            this.Document.Remove(offset, length);
        }

        public void 移除字符(int offset, int length)
        {
            Remove(offset, length);
        }

        /// <summary>
        /// this.Document.IndexOf(char c, int startIndex, int count) 方法别名。
        /// </summary>
        /// <param name="c"></param>
        /// <param name="startIndex"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public int IndexOf(char c, int startIndex, int count)
        {
            if (this.Document == null) return -1;

            return this.Document.IndexOf(c, startIndex, count);
        }

        /// <summary>
        /// 编辑器获得焦点时自动打开输入法。
        /// </summary>
        void MarkDownEditorBase_GotFocus(object sender, RoutedEventArgs e)
        {
            // Globals.SwitchInputMethod(true);  // 这个行为十分诡异，加上反而会导致输入法被关闭。
        }

        /// <summary>
        /// 查找面板是否处于开启状态。
        /// * AvalonEdit的查找面板有 Bug，经常无法输入中文。
        /// 所以实际上没有用它，此属性已无用处。
        /// </summary>
        public bool IsSearchPanelOpened
        {
            get
            {
                if (searchPanel == null) return false;

                if (searchPanel.IsClosed) return false;

                return true;
            }
        }

        /// <summary>
        /// [已废弃]AvalonEdit 实现的查找面板，出现在编辑区右上角。有Bug，常无法输入中文。
        /// </summary>
        private SearchPanel searchPanel = null;

        /// <summary>
        /// [已废弃]关闭 AvalonEdit 自己实现的搜索面板。
        /// </summary>
        public void CloseSearchPanel()
        {
            if (searchPanel == null) return;

            searchPanel.Close();
        }

        /// <summary>
        /// [已废弃]查找文本。
        /// </summary>
        public void Find()
        {
            searchPanel.SearchPattern = this.TextArea.Selection.GetText();

            if (searchPanel.IsClosed) searchPanel.Open();

            searchPanel.Reactivate();
        }

        /// <summary>
        /// 在当前编辑器中查找指定的文本。注意：此方法以当前选择区为起点，但不会查找被选中的文本——只会查找在当前选择区之前或之后的文本。
        /// 在 isPreview 为 true 时，逆序查找选择区之前的文本；否则向后查找。
        /// </summary>
        /// <param name="text">不能为空字符串。</param>
        /// <param name="isPreview">是否逆序查找。</param>
        /// <returns>如果找到，返回位置索引，如果找不到，返回 -1。</returns>
        public int Find(string text, bool isPreview = false)
        {
            if (string.IsNullOrEmpty(text))
            {
                return -1;
            }

            if (isPreview)
            {
                // 倒找文本
                var preText = this.Document.GetText(0, this.sel_start);
                return preText.LastIndexOf(text);
            }

            var endOffset = this.SelectionEnd;
            var nextText = this.Document.GetText(endOffset, this.Document.TextLength - endOffset);
            return endOffset + nextText.IndexOf(text);
        }

        /// <summary>
        /// Find() 的别名，方便通过自定义脚本调用而已。
        /// 
        /// 在当前编辑器中查找指定的文本。注意：此方法以当前选择区为起点，但不会查找被选中的文本——只会查找在当前选择区之前或之后的文本。
        /// 在 isPreview 为 true 时，逆序查找选择区之前的文本；否则向后查找。
        /// </summary>
        /// <param name="text">不能为空字符串。</param>
        /// <param name="isPreview">是否逆序查找。</param>
        /// <returns>如果找到，返回位置索引，如果找不到，返回 -1。</returns>
        public int find(string text, bool isPreview = false) => Find(text, isPreview);

        /// <summary>
        /// [已废弃]逆向查找。
        /// </summary>
        public void FindPreview()
        {
            searchPanel.FindPrevious();
        }

        /// <summary>
        /// [已废弃]正向查找。
        /// </summary>
        public void FindNext()
        {
            searchPanel.FindNext();
        }

        /// <summary>
        /// 文档内容变化时更改 IsModified 属性的值。
        /// </summary>
        //void MarkdownEditorBase_TextChanged(object sender, EventArgs e)
        //{
        //    this.IsModified = true;//勉强解决。
        //}

        #region 自动完成

        /// <summary>
        /// 判断此文本是否与试题相关。
        /// </summary>
        /// <param name="lineText">要检查的文本行。</param>
        public static bool IsStartWithTestPaperKeyWord(string lineText)
        {
            if (string.IsNullOrEmpty(lineText)) return false;

            if (lineText.StartsWith("　　试题＞＞")) return true;
            if (lineText.StartsWith("　　答案＞＞")) return true;
            if (lineText.StartsWith("　　错项＞＞")) return true;
            if (lineText.StartsWith("　　解析＞＞")) return true;
            if (lineText.StartsWith("　考查点＞＞")) return true;
            if (lineText.StartsWith("　　试题＞＞")) return true;
            if (lineText.StartsWith("＜＜材料＞＞")) return true;
            if (lineText.StartsWith("＜＜出处＞＞")) return true;
            if (lineText.StartsWith("＜＜问题＞＞")) return true;
            if (lineText.StartsWith("＜＜＜信息＞")) return true;
            if (lineText.StartsWith("　类型＞＞")) return true;
            if (lineText.StartsWith("　标题＞＞")) return true;
            if (lineText.StartsWith("　日期＞＞")) return true;
            if (lineText.StartsWith("　辑录＞＞")) return true;
            if (lineText.StartsWith("　作者＞＞")) return true;
            if (lineText.StartsWith("　电邮＞＞")) return true;
            if (lineText.StartsWith("　备注＞＞")) return true;
            if (lineText.StartsWith("　辑录＞＞")) return true;
            if (lineText.StartsWith("＜信息＞＞＞")) return true;

            return false;
        }

        private CompletionWindow completionWindow;
        /// <summary>
        /// 自动完成窗口。
        /// </summary>
        public CompletionWindow CompletionWindow
        {
            get
            {
                return completionWindow;
            }
        }

        /// <summary>
        /// 关闭自动完成窗口。
        /// </summary>
        public void InvalidateCompletionWindow()
        {
            if (this.completionWindow == null) return;
            this.completionWindow.Close();
            this.completionWindow = null;
        }

        /// <summary>
        /// 用于在切换工作区时、工作区管理器发生变化时重新载入关于各 Markdown 文件的自动完成列表。
        /// </summary>
        /// <param name="workspaceTreeView">应传入工作区管理器</param>
        public void LoadLinkAutoCompletionItems(TreeView workspaceTreeView, bool isEnableAutoComplete)
        {
            if (this.CompletionWindow == null) return;

            IList<ICompletionData> dataList = CompletionWindow.CompletionList.CompletionData;
            for (int i = dataList.Count - 1; i >= 0; i--)
            {
                var item = dataList[i] as CustomCompletionData;
                if (item == null) continue;

                if (item.Type == CompleteItemType.MarkdownFileLink ||
                    item.Type == CompleteItemType.MetaMarkdownFileLink ||
                    item.Type == CompleteItemType.ImageResourceLink) dataList.RemoveAt(i);
            }//先清除

            if (workspaceTreeView == null || workspaceTreeView.Items.Count <= 0 || isEnableAutoComplete == false) return;

            var wtvi = workspaceTreeView.Items[0] as WorkspaceTreeViewItem;
            if (wtvi != null)
            {
                AddLinksAutoCompleteItem(wtvi, ref dataList);
            }
        }

        /// <summary>
        /// 计算两个文档间的相对位置，即要能从当前文档找到目标链接文档。
        /// </summary>
        /// <param name="thisDocPath">要插入链接的文档的完全路径。</param>
        /// <param name="linkDestDocPath">要被链接的文档的完全路径。</param>
        /// <param name="withHtmlSuffix">true，返回 .html 后缀；false 返回 .md 后缀。</param>
        /// <returns></returns>
        public static string GetRelativePath(string thisDocPath, string linkDestDocPath, bool withHtmlSuffix)
        {
            try
            {
                var thisDocInfo = new FileInfo(thisDocPath);
                var linkDestDocInfo = new FileInfo(linkDestDocPath);

                //用 ~ 代替 _ 风险太高了！！
                //if (linkDestDocInfo.Name.StartsWith("_"))
                //{
                //    linkDestDocPath = linkDestDocInfo.Directory.FullName + "\\~" + linkDestDocInfo.Name.Substring(1);
                //}

                //基本思路：
                //当前文档路径：       C:\abc\def\ghi\jkl\lmn.md
                //要链接到的文件路径： C:\abc\def\mnl\opq\xyz.md
                //办法是先找到两者开始分叉的层级，然后算出当前文档得向上移动几层。

                var parentInfo = thisDocInfo.Directory;
                var root = parentInfo.Root;
                var prefix = "";

                while (parentInfo != null)
                {
                    var parentPath = parentInfo.FullName;
                    if (parentPath.EndsWith("\\") == false)
                    {
                        parentPath += "\\";
                    }

                    if (false == linkDestDocPath.StartsWith(parentPath, StringComparison.CurrentCultureIgnoreCase))
                    {
                        prefix += "../";
                    }
                    else
                    {
                        //prefix += "../";//这行很奇怪，之前控制台应用程序调试时没问题，但转移到这里却会嫌多。
                        var length = parentInfo.FullName.Length;
                        if (parentInfo.FullName.EndsWith("\\") == false && parentInfo.FullName.EndsWith("/") == false)
                            length++;
                        var tail = linkDestDocPath.Substring(length);  //加的1是指 \
                        var result = (prefix + tail).Replace("\\", "/");
                        if (withHtmlSuffix && result.EndsWith(".md", StringComparison.CurrentCultureIgnoreCase))
                        {
                            result = result.Substring(0, result.Length - 3) + ".html";
                        }

                        return result;
                    }
                    parentInfo = parentInfo.Parent;
                }
                return null;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// 这里的 Link 包括普通 Markdwon 文件、目录元文件、图像资源文件。
        /// </summary>
        /// <param name="item"></param>
        /// <param name="dataList"></param>
        private void AddLinksAutoCompleteItem(WorkspaceTreeViewItem item, ref IList<ICompletionData> dataList)
        {
            if (item == null || dataList == null) return;
            var editor = this.MasterEditor;
            if (editor == null) return;
            var thisFullPath = editor.FullFilePath;
            if (string.IsNullOrWhiteSpace(thisFullPath)) return;

            var title = item.Title;
            if (string.IsNullOrWhiteSpace(title))
            {
                try
                {
                    var fi = new FileInfo(item.FullPath);
                    title = fi.Name;
                    var lastOfIndex = title.LastIndexOf('.');
                    if (lastOfIndex >= 0)
                    {
                        title = title.Substring(0, lastOfIndex);
                    }
                }
                catch
                {
                    return;
                }

                if (item.IsMetaDirectoryPath)
                {
                    //添加目录元文件对应的Html文件的路径。
                    var metaFilePath = item.MetaHtmlFilePath;
                    if (metaFilePath == null) return;

                    var relativePath = GetRelativePath(thisFullPath, metaFilePath, true);
                    dataList.Add(new CustomCompletionData(title, $"链接到：{title}",
                        $"[{title}]({relativePath} \"{title}\")")
                    {
                        Type = CompleteItemType.MetaMarkdownFileLink,
                        Image = linkIcon,
                    });
                }
                else if (item.IsMarkdownFilePath)
                {
                    //添加Markdown文件对应的Html文件的路径。
                    var relativePath = GetRelativePath(thisFullPath, item.FullPath, true);
                    dataList.Add(new CustomCompletionData(title, $"链接到：{title}",
                        $"[{title}]({relativePath} \"{title}\")")
                    {
                        Type = CompleteItemType.MarkdownFileLink,
                        Image = linkIcon,
                    });
                }
                else if (item.IsImageFileExist)
                {
                    //添加Markdown文件对应的Html文件的路径。
                    var relativePath = GetRelativePath(thisFullPath, item.FullPath, true);
                    dataList.Add(new CustomCompletionData(title, $"链接到图像：{title}",
                        $"[{title}]({relativePath} \"{title}\")")
                    {
                        Type = CompleteItemType.ImageResourceLink,
                        Image = linkIcon,
                    });
                }

                if (item.Items.Count > 0)
                {
                    foreach (var si in item.Items)
                    {
                        var subItem = si as WorkspaceTreeViewItem;
                        if (subItem == null) continue;
                        AddLinksAutoCompleteItem(subItem, ref dataList);
                    }
                }
            }
        }

        private Dictionary<string, string> textCommands = new Dictionary<string, string>();
        /// <summary>
        /// 某些特殊的、临时使用的文本命令。例如：“按空格键时，如果满足特定条件，就换新行”等。
        /// </summary>
        public Dictionary<string, string> TextCommands { get { return textCommands; } }

        /// <summary>
        /// 向自动完成窗口添加可用的条目。
        /// </summary>
        /// <param name="entry">自动完成提示条目。</param>
        public void AddEntryToCompletionWindow(EnToChEntry entry)
        {
            if (this.CompletionWindow == null) return;
            IList<ICompletionData> dataList = CompletionWindow.CompletionList.CompletionData;
            dataList.Add(new CustomCompletionData(entry.Chinese,
                        $"{entry.Chinese}（{entry.Alpha}，{entry.English}）" + (entry.Comment == string.Empty ? null : entry.Comment),
                        $"{entry.Chinese}（{entry.Alpha}，{entry.English}）"));
            dataList.Add(new CustomCompletionData(entry.English,
                        $"{entry.Chinese}（{entry.Alpha}，{entry.English}）" + (entry.Comment == string.Empty ? null : entry.Comment),
                        $"{entry.Chinese}（{entry.Alpha}，{entry.English}）"));
        }

        /// <summary>
        /// 在向活动编辑器中输入文本时，根据情况弹出自动提示。
        /// </summary>
        void TextArea_TextEntering(object sender, TextCompositionEventArgs e)
        {
            if (e.Text == "." || e.Text == "。")
            {
                return;
            }

            var curLine = CurrentDocumentLine();
            if (curLine.LineNumber == 1 && CustomMarkdownSupport.IsTaskLine(CurrentLine()))
            // 第 1 行通常是完成状态行，不需要跳转，反而经常需要用 Tab 来自动将 Date 转换成日期。
            {
                return;
            }

            if (IsAutoCompletionEnabled == false) return;
            if ("\r\n 　`~!@#$%^&*()_+-=]\\}|;\':\",.<>?～·！＠＃￥％……＆＊（）｛｝｜【】、：＂；＇，．／＜＞？".Contains(e.Text)) return;
            // [{/ 现在要添加自动完成功能了。2022.6.24

            var line = this.Document.GetLineByOffset(this.CaretOffset);
            if (line == null) return;

            if (completionWindow == null)
            {
                completionWindow = new CompletionWindow(this.TextArea)
                {
                    MinHeight = 34,
                    MinWidth = 100,
                    Width = 240,
                };
                TextOptions.SetTextRenderingMode(completionWindow.CompletionList, TextRenderingMode.Aliased);
            }

            IList<ICompletionData> data = CompletionWindow.CompletionList.CompletionData;

            //清除自动完成列表并重新添加其中的项目。
            data.Clear();

            if (IsExamEnabled)//不需要这个，强制换行即可。 && this.CaretOffset - line.Offset <= 3)
            {
                #region 添加试题需要的提示项

                var st = new CustomCompletionData("试题", "每个试题都应以此开头。", "\r\n　　试题＞＞") { Image = examIcon, };
                var da = new CustomCompletionData("答案", "每个答案都应以此为开头。", "\r\n　　答案＞＞") { Image = examIcon, };//缩进不采用Tab是因为易乱。
                var cx = new CustomCompletionData("错项", "每个备选的错项都应以此为开头。", "\r\n　　错项＞＞") { Image = examIcon, };
                var jx = new CustomCompletionData("解析", "每个解析条目都应以此开头。", "\r\n　　解析＞＞") { Image = examIcon, };
                var fx = new CustomCompletionData("分析", "每个解析条目都应以此开头。\r\n※ 只是为了左手输入方便才提供，功能与“解析头”相同。", "\r\n　　解析＞＞") { Image = examIcon, };
                // 加这个“分析”头是为了方便左手输入（用 fx 两个字母不必离开左手键盘区——它的作用和 jx 是一样的，但不必跳到键盘右侧输入。
                // 超高频的应考虑输入按键位置。

                var kcd = new CustomCompletionData("考点", "每个考查点条目都应以此开头。", "\r\n　考查点＞＞") { Image = examIcon, };
                var cl = new CustomCompletionData("材料", "主观题每则材料均应以此开头。", "\r\n＜＜材料＞＞") { Image = examIcon, };
                var sc = new CustomCompletionData("素材", "主观题每则材料均应以此开头。\r\n※ 只是为了左手输入方便才提供，功能与“材料头”相同。", "\r\n＜＜材料＞＞") { Image = examIcon, };
                // 加这个“素材”头是为了方便左手输入（用 sc 两个字母不必离开左手键盘区——它的作用和 cl 是一样的，但不必跳到键盘右侧输入。
                // 超高频的应考虑输入按键位置。

                var cc = new CustomCompletionData("出处", "主观题每则材料的出处。", "\r\n＜＜出处＞＞") { Image = examIcon, };
                var wt = new CustomCompletionData("问题", "问题集中可包含多个问题。", "\r\n＜＜问题＞＞") { Image = examIcon, };

                var wb = new CustomCompletionData("题尾：〓", "单行填空题不需要添加。", "\r\n〓〓〓〓〓〓\r\n") { Image = examIcon, };//这样设计是保证全角等于号与半角等于号都有用。
                var xx = new CustomCompletionData("信息", "【可省略】用于文档开头指明文档相关信息，如章节、创建者、创建时间等等。",
                    "＜＜＜信息＞\r\n" +
                    "　类型＞＞＜＜类型\r\n" +
                    "　标题＞＞请输入文档标题＜＜标题\r\n" +
                    "　日期＞＞＜＜日期\r\n" +
                    "　辑录＞＞＜＜辑录\r\n" +
                    "　电邮＞＞＜＜电邮\r\n" +
                    "　备注＞＞＜＜备注\r\n" +
                    "＜信息＞＞＞\r\n\r\n")
                { Image = documentInfoIcon, };

                st.Completed += St_Completed;                // 要去除头部 1. 这样的序号。
                cl.Completed += Cl_Completed;                // 去除“材料  ”字样。
                sc.Completed += Cl_Completed;                // 去除“材料  ”字样。
                da.Completed += DaOrJx_Completed;            // 去除头部 （1） 这样的序号，再去除“答案：”字样。
                jx.Completed += DaOrJx_Completed;            // 去除头部 （1） 这样的序号，再去除“解析：”字样。
                fx.Completed += DaOrJx_Completed;            // 去除头部 （1） 这样的序号，再去除“解析：”字样。
                kcd.Completed += DaOrJx_Completed;           // 去除头部 （1） 这样的序号，再去除“考查点：”字样。
                wt.Completed += Element_Completed;           // 去除头部 （1） 这样的序号。
                                                             //cx.Completed += Item_Completed;
                cc.Completed += Cc_Completed;
                //wb.Completed += Item_Completed;
                xx.Completed += xx_Completed;

                data.Add(st);
                data.Add(da);
                data.Add(cx);
                data.Add(jx);
                data.Add(fx);
                data.Add(kcd);
                data.Add(cl);
                data.Add(sc);
                data.Add(cc);
                data.Add(wt);
                //data.Add(new MyCompletionData("【方括弧对】", "试题中应包含一个或多个填空项目，这些填空项用一对方括弧包围。","【】"));//效果不好。
                data.Add(wb);
                data.Add(xx);

                #endregion
            }

            #region 添加水平线相关的几个项

            var spx1 = new CustomCompletionData("水平线1", "水平线 01", "\r\n<hr class=\"hr1\"/>\r\n\r\n") { Image = horizontalLineIcon, };
            var spx2 = new CustomCompletionData("水平线2", "水平线 02", "\r\n<hr class=\"hr2\"/>\r\n\r\n") { Image = horizontalLineIcon, };
            var spx3 = new CustomCompletionData("水平线3", "水平线 03", "\r\n<hr class=\"hr3\"/>\r\n\r\n") { Image = horizontalLineIcon, };
            var spx4 = new CustomCompletionData("水平线4", "水平线 04", "\r\n<hr class=\"hr4\"/>\r\n\r\n") { Image = horizontalLineIcon, };
            var spx5 = new CustomCompletionData("水平线5", "水平线 05", "\r\n<hr class=\"hr5\"/>\r\n\r\n") { Image = horizontalLineIcon, };

            data.Add(spx1);
            data.Add(spx2);
            data.Add(spx3);
            data.Add(spx4);
            data.Add(spx5);

            #endregion

            #region 添加几个内置环境变量

            var gVTitle = new CustomCompletionData("/*Title*/", "[环境变量]引用当前文档的标题文本。", "/*Title*/") { Image = envalueIcon, };
            var gVFileName = new CustomCompletionData("/*FileName*/", "[环境变量]引用当前文件的短名。", "/*FileName*/") { Image = envalueIcon, };
            var gVPath = new CustomCompletionData("/*Path*/", "[环境变量]引用当前文件的路径。", "/*Path*/") { Image = envalueIcon, };
            var gVFullName = new CustomCompletionData("/*FullName*/", "[环境变量]引用当前文件的路径。", "/*FullName*/") { Image = envalueIcon, };
            var gVFullPath = new CustomCompletionData("/*FullPath*/", "[环境变量]引用当前文件的路径。", "/*FullPath*/") { Image = envalueIcon, };

            var gVDirectoryName = new CustomCompletionData("/*DirectoryName*/", "[环境变量]引用当前文件所在的目录的短名。", "/*DirectoryName*/") { Image = envalueIcon, };
            var gVDirectoryFullName = new CustomCompletionData("/*DirectoryFullName*/", "[环境变量]引用当前文件所在的目录的路径。", "/*DirectoryFullName*/") { Image = envalueIcon, };
            var gVDirectoryPath = new CustomCompletionData("/*DirectoryPath*/", "[环境变量]引用当前文件所在的目录的路径。", "/*DirectoryPath*/") { Image = envalueIcon, };
            var gVDirectoryFullPath = new CustomCompletionData("/*DirectoryFullPath*/", "[环境变量]引用当前文件所在的目录的路径。", "/*DirectoryFullPath*/") { Image = envalueIcon, };
            var gVWorkspaceTitle = new CustomCompletionData("/*WorkspaceTitle*/", "[环境变量]引用当前文件所在工作区元文件的标题文本。", "/*WorkspaceTitle*/") { Image = envalueIcon, };

            var gVWorkspaceName = new CustomCompletionData("/*WorkspaceName*/", "[环境变量]引用当前工作区目录的短名。", "/*WorkspaceName*/") { Image = envalueIcon, };
            var gVWorkspacePath = new CustomCompletionData("/*WorkspacePath*/", "[环境变量]引用当前工作区目录的路径。", "/*WorkspacePath*/") { Image = envalueIcon, };
            var gVWorkspaceFullName = new CustomCompletionData("/*WorkspaceFullName*/", "[环境变量]引用当前工作区目录的路径。", "/*WorkspaceFullName*/") { Image = envalueIcon, };
            var gVWorkspaceFullPath = new CustomCompletionData("/*WorkspaceFullPath*/", "[环境变量]引用当前工作区目录的路径。", "/*WorkspaceFullPath*/") { Image = envalueIcon, };
            var gVRelativePath = new CustomCompletionData("/*RelativePath*/", "[环境变量]引用当前文档相对于工作区目录的路径。", "/*RelativePath*/") { Image = envalueIcon, };

            var gVPageHeader = new CustomCompletionData("/*PageHeader*/", "[环境变量]引用当前文档的页眉文本。", "/*PageHeader*/") { Image = envalueIcon, };
            var gVPageFooter = new CustomCompletionData("/*PageFooter*/", "[环境变量]引用当前文档的页脚文本。", "/*PageFooter*/") { Image = envalueIcon, };
            var gVNav = new CustomCompletionData("/*Nav*/", "[环境变量]在当前位置生成指向各上级目录节点元文件的导航链接列表。", "/*Nav*/") { Image = envalueIcon, };
            var gVSubLinksList = new CustomCompletionData("/*SubLinksList*/", "[环境变量]在当前位置生成指向当前节点直接下级的目录、文件的链接列表。\r\n（此环境变量通常出现在目录元文件末尾，无须手动添加。）", "/*SubLinksList*/") { Image = envalueIcon, };

            data.Add(gVTitle);
            data.Add(gVFileName);
            data.Add(gVPath);
            data.Add(gVFullName);
            data.Add(gVFullPath);

            data.Add(gVDirectoryName);
            data.Add(gVDirectoryFullName);
            data.Add(gVDirectoryPath);
            data.Add(gVDirectoryFullPath);
            data.Add(gVWorkspaceTitle);

            data.Add(gVWorkspaceName);
            data.Add(gVWorkspacePath);
            data.Add(gVWorkspaceFullName);
            data.Add(gVWorkspaceFullPath);
            data.Add(gVRelativePath);

            data.Add(gVPageHeader);
            data.Add(gVPageFooter);
            data.Add(gVNav);
            data.Add(gVSubLinksList);

            //添加用户环境变量
            foreach (var d in Globals.MainWindow.UserEnvironmentValues)
            {
                data.Add(new CustomCompletionData($"/*{d.Key}*/", "[用户环境变量]", $"/*{d.Key}*/"));
            }

            //添加工和区环境变量
            foreach (var d in Globals.MainWindow.WorkspaceEnvironmentValues)
            {
                data.Add(new CustomCompletionData($"/*{d.Key}*/", "[工作区环境变量]", $"/*{d.Key}*/"));
            }
            #endregion

            #region 添加指定Markdown元素

            var anchor = new CustomCompletionData("锚", "锚", "\r\n[](@请输入锚的标识符)\r\n") { Image = anchorIcon, };
            anchor.Completed += Anchor_Completed;
            data.Add(anchor);

            var link = new CustomCompletionData("链接符", "适用于链接工作区以外的地址", "[请输入链接名称]()") { Image = emptyLinkIcon, };
            link.Completed += Link_Completed;
            data.Add(link);

            var addNewLine = LeftText.Length > 0;

            var squareBlock = new CustomCompletionData("方块   [  .../...]", "插入方块区域", (addNewLine ? "\r\n" : "") + "[ \r\n\r\n\r\n\r\n] \r\n") { Image = squareIcon, };
            squareBlock.Completed += SquareBlock_Completed;
            data.Add(squareBlock);

            var squareBlockWithId = new CustomCompletionData("id  块 [ @.../...]", "插入带 id 的方块区域", (addNewLine ? "\r\n" : "") + "[ (@add_id_at_here)\r\n\r\n\r\n\r\n] \r\n") { Image = squareIcon, };
            squareBlockWithId.Completed += SquareBlockWithId_Completed;
            data.Add(squareBlockWithId);

            var squareBlockWithClass = new CustomCompletionData("定制样式块 [ &.../...]", "插入带 class 的方块区域", (addNewLine ? "\r\n" : "") + "[ (&add_class_at_here)\r\n\r\n\r\n\r\n] \r\n") { Image = squareIcon, };
            squareBlockWithClass.Completed += SquareBlockWithClass_Completed;
            data.Add(squareBlockWithClass);

            var squareBlockNumber = new CustomCompletionData("定宽块 [ n.../...]", "插入带边框宽度的 div 块", (addNewLine ? "\r\n" : "") + "[ 请在此处输入数字，范围是 0-9\r\n\r\n\r\n\r\n] \r\n") { Image = squareIcon, };
            squareBlockNumber.Completed += SquareBlockNumber_Completed;
            data.Add(squareBlockNumber);

            var squareBlockQuestion = new CustomCompletionData("问号块 [ ?.../...]", "插入 <div class=\"ibox_question\" /> 块", (addNewLine ? "\r\n" : "") + "[ ?\r\n\r\n\r\n\r\n] \r\n") { Image = squareIcon, };
            squareBlockQuestion.Completed += SquareBlock_Completed;
            data.Add(squareBlockQuestion);

            var squareBlockExclamation = new CustomCompletionData("叹号块 [ !.../...]", "插入 <div class=\"ibox_exclamation\" /> 块", (addNewLine ? "\r\n" : "") + "[ !\r\n\r\n\r\n\r\n] \r\n") { Image = squareIcon, };
            squareBlockExclamation.Completed += SquareBlock_Completed;
            data.Add(squareBlockExclamation);

            var squareBlockPlus = new CustomCompletionData("加号块 [ +.../...]", "插入 <div class=\"ibox_plus\" /> 块", (addNewLine ? "\r\n" : "") + "[ +\r\n\r\n\r\n\r\n] \r\n") { Image = squareIcon, };
            squareBlockPlus.Completed += SquareBlock_Completed;
            data.Add(squareBlockPlus);

            var squareBlockMinus = new CustomCompletionData("减号块 [ -.../...]", "插入 <div class=\"ibox_minus\" /> 块", (addNewLine ? "\r\n" : "") + "[ -\r\n\r\n\r\n\r\n] \r\n") { Image = squareIcon, };
            squareBlockMinus.Completed += SquareBlock_Completed;
            data.Add(squareBlockMinus);

            var squareBlockStar = new CustomCompletionData("星号块 [ *.../...]", "插入 <div class=\"ibox_star\" /> 块", (addNewLine ? "\r\n" : "") + "[ *\r\n\r\n\r\n\r\n] \r\n") { Image = squareIcon, };
            squareBlockStar.Completed += SquareBlock_Completed;
            data.Add(squareBlockStar);

            var squareBlockCaret = new CustomCompletionData("插入块 [ ^.../...]", "插入 <div class=\"ibox_caret\" /> 块", (addNewLine ? "\r\n" : "") + "[ *\r\n\r\n\r\n\r\n] \r\n") { Image = squareIcon, };
            squareBlockCaret.Completed += SquareBlock_Completed;
            data.Add(squareBlockCaret);

            var squareBlockWithStyle = new CustomCompletionData("样式块 [ $.../...]", "插入带简单样式表的方块\r\n说明：简单样式表除前景色、背景色有前后位置的\r\n　　区别外，其余几种格式没有对书写位置的要求。", (addNewLine ? "\r\n" : "") + "[ $ 楷体，1.5em，加粗，倾斜，红色，黄色\r\n\r\n\r\n\r\n] \r\n") { Image = squareIcon, };
            squareBlockWithStyle.Completed += SquareBlockWithStyle_Completed;
            data.Add(squareBlockWithStyle);

            var squareBlockWithTitle = new CustomCompletionData("标题块 [ %.../...]", "插入带标题的方块\r\n说明：便于的编写“标题→内容→小结”这样的片段。", (addNewLine ? "\r\n" : "") + "[ % 请输入标题文本\r\n\r\n\r\n\r\n] \r\n") { Image = squareIcon, };
            squareBlockWithTitle.Completed += SquareBlockWithTitle_Completed;
            data.Add(squareBlockWithTitle);

            var region = new CustomCompletionData("自定义折叠区 { .../...}", "插入传统自定义折叠区域", "\r\n\r\nregion { 请输入折叠区标题\r\n\r\n\r\n\r\n}region \r\n\r\n") { Image = collapseAreaIcon, };
            region.Completed += Region_Completed;
            data.Add(region);

            var regionWithId = new CustomCompletionData("id 折叠区 { @.../...}", "插入带 id 的折叠区", "\r\n\r\n{ <!--需要标题就写在这里，跟个空格再写 id-->  (@add_id_at_here)\r\n\r\n\r\n\r\n} \r\n") { Image = collapseAreaIcon, };
            regionWithId.Completed += RegionWithId_Completed;
            data.Add(regionWithId);

            var regionWithClass = new CustomCompletionData("定制样式折叠区 { &.../...}", "插入带 class 的折叠区", "\r\n\r\n{ (&add_class_at_here)\r\n\r\n\r\n\r\n} \r\n") { Image = collapseAreaIcon, };
            regionWithClass.Completed += RegionWithClass_Completed;
            data.Add(regionWithClass);

            var regionCollapse = new CustomCompletionData("折叠区 { + .../...}", "插入一个折叠态的自定义折叠区", (addNewLine ? "\r\n" : "") + "{ + \r\n\r\n\r\n\r\n} \r\n") { Image = collapseAreaIcon, };
            regionCollapse.Completed += RegionCollapse_Completed;
            data.Add(regionCollapse);

            var regionExpand = new CustomCompletionData("展开区 { - .../...}", "插入一个展开态的自定义折叠区", (addNewLine ? "\r\n" : "") + "{ - \r\n\r\n\r\n\r\n} \r\n") { Image = collapseAreaIcon, };
            regionExpand.Completed += RegionExpand_Completed;
            data.Add(regionExpand);

            var regionSingleLineCollapse = new CustomCompletionData("单行折叠区 { + ...}", "插入一个折叠态的单行折叠区", (addNewLine ? "\r\n" : "") + "{ + 【】\r\n\r\n\r\n\r\n} \r\n") { Image = collapseAreaIcon, };
            regionSingleLineCollapse.Completed += RegionSingleLineCollapse_Completed;
            data.Add(regionSingleLineCollapse);

            var regionSingleLineExpand = new CustomCompletionData("单行展开区 { - ...}", "插入一个展开态的单行折叠区", (addNewLine ? "\r\n" : "") + "{ - 【】\r\n\r\n\r\n\r\n} \r\n") { Image = collapseAreaIcon, };
            regionSingleLineExpand.Completed += RegionSingleLineExpand_Completed;
            data.Add(regionSingleLineExpand);

            #endregion

            #region 添加英译中词典

            if (IsEnToChineseDictEnabled)
            {
                //new CustomCompletionData("锚", "锚", "\r\n[](@请输入锚的标识符)\r\n");
                foreach (var entry in Globals.MainWindow.Dictionary)
                {
                    data.Add(new CustomCompletionData(entry.Chinese,
                                $"{entry.Chinese}（{entry.Alpha}，{entry.English}）" + (entry.Comment == string.Empty ? null : entry.Comment),
                                $"{entry.Chinese}（{entry.Alpha}，{entry.English}）")
                    { Image = dictionEntryIcon, });
                    data.Add(new CustomCompletionData(entry.English,
                                $"{entry.Chinese}（{entry.Alpha}，{entry.English}）" + (entry.Comment == string.Empty ? null : entry.Comment),
                                $"{entry.Chinese}（{entry.Alpha}，{entry.English}）")
                    { Image = dictionEntryIcon, });
                }
            }

            #endregion

            #region 添加对工作区中各文件的链接
            LoadLinkAutoCompletionItems(Globals.MainWindow.tvWorkDirectory, Globals.MainWindow.IsAutoCompletionEnabled);
            #endregion

            CompletionWindow.Show();
            CompletionWindow.Closed += delegate
            {
                completionWindow = null;
            };
        }

        private void Cc_Completed(object sender, EventArgs e)
        {
            var reg = new Regex(@"^[ 　\t\-－—]{1,}");
            var txt = this.right_text;
            var match = reg.Match(txt);
            if (match.Success)
            {
                this.Remove(this.SelectionStart, match.Length);
            }
        }

        private void SquareBlockWithTitle_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 19, 7);
        }

        private void RegionSingleLineExpand_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 13, 0);
        }

        private void RegionSingleLineCollapse_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 13, 0);
        }

        private void RegionExpand_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 12, 0);
        }

        private void RegionCollapse_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 12, 0);
        }

        private void SquareBlockNumber_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 28, 16);
        }

        private void SquareBlockWithId_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 27, 14);
        }

        private void SquareBlockWithClass_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 30, 17);
        }

        private void SquareBlock_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 8, 0);
        }

        private void SquareBlockWithStyle_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 32, 20);
        }

        private void DaOrJx_Completed(object sender, EventArgs e)
        {
            if (SelectionLength > 0) return;  // 防止意外

            var curLine = this.Document.GetLineByOffset(this.SelectionStart);
            var tailText = this.Document.GetText(this.SelectionStart, curLine.EndOffset - this.SelectionStart);

            Regex reg = new Regex(@"^((【(答案|解析|考查点|详解|(试[题卷])?(分析)?)】)|((答案|解析|考查点|详解|(试[题卷])?(分析)?)[:：]))?[ 　\t]{0,}([(（][ 　\t]{0,}\d{1,}[ 　\t]{0,}[）)][ 　\t]{0,})?");
            var match = reg.Match(tailText);
            if (match.Success)
            {
                this.Document.Remove(this.SelectionStart, match.Length);
            }
        }

        private void Cl_Completed(object sender, EventArgs e)
        {
            if (SelectionLength > 0) return;  // 防止意外

            var curLine = this.Document.GetLineByOffset(this.SelectionStart);
            var tailText = this.Document.GetText(this.SelectionStart, curLine.EndOffset - this.SelectionStart);

            // 注意：这种带序号的要在前面处理。
            Regex regCl = new Regex(@"[材史]料(([一二三四五六七八九十]|\d)){1,}([ 　\t:：]*)");
            var matchCl = regCl.Match(tailText);
            if (matchCl.Success)
            {
                this.Document.Replace(this.SelectionStart, matchCl.Length, "");
                // 不保留编号，编译时会自动添加。 matchCl.Value.Trim(new char[] { ' ', '　', '\t', ':', '：', }) + "　");
                return;
            }

            Regex reg = new Regex(@"^材料[ 　\t:：]{0,}");
            var match = reg.Match(tailText);
            if (match.Success)
            {
                this.Document.Remove(this.SelectionStart, match.Length);
            }
        }

        private void Element_Completed(object sender, EventArgs e)
        {
            if (SelectionLength > 0) return;  // 防止意外

            var curLine = this.Document.GetLineByOffset(this.SelectionStart);
            var tailText = this.Document.GetText(this.SelectionStart, curLine.EndOffset - this.SelectionStart);

            Regex reg = new Regex(@"^[ 　\t]{0,}[(（][ 　\t]{0,}\d{1,}[ 　\t]{0,}[）)][ 　\t]{0,}");
            var match = reg.Match(tailText);
            if (match.Success)
            {
                this.Document.Remove(this.SelectionStart, match.Length);
            }
        }

        private void St_Completed(object sender, EventArgs e)
        {
            if (SelectionLength > 0) return;  // 防止意外

            var curLine = this.Document.GetLineByOffset(this.SelectionStart);
            var tailText = this.Document.GetText(this.SelectionStart, curLine.EndOffset - this.SelectionStart);

            Regex reg = new Regex(@"^[ 　\t]{0,}\d{1,}[\.．][ 　\t]{0,}(?=[^0-9０-９])");
            var match = reg.Match(tailText);
            if (match.Success)
            {
                this.Document.Remove(this.SelectionStart, match.Length);
            }
        }

        public static readonly ImageSource documentInfoIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/document-info.png"));
        public static readonly ImageSource examIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/options.png"));
        public static readonly ImageSource horizontalLineIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/insert-horizontal-rule.png"));
        public static readonly ImageSource squareIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/square.png"));
        public static readonly ImageSource anchorIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/link.png"));
        public static readonly ImageSource collapseAreaIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/collapseArea.png"));
        public static readonly ImageSource emptyLinkIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/link-edit.png"));
        public static readonly ImageSource dictionEntryIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/book-key.png"));
        public static readonly ImageSource linkIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/document-link.png"));
        public static readonly ImageSource envalueIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/envalue.png"));

        /// <summary>
        /// 自动完成链接文本标记。
        /// </summary>
        private void Link_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 10, 7);
        }

        /// <summary>
        /// 自动完成“region”文本标记。
        /// </summary>
        private void Region_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 28, 8);
        }

        /// <summary>
        /// 自动完成“region”文本标记。
        /// </summary>
        private void RegionWithId_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 27, 14);
        }

        private void RegionWithClass_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 30, 17);
        }

        /// <summary>
        /// 自动完成锚文本标记。
        /// </summary>
        private void Anchor_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 11, 8);
        }

        /// <summary>
        /// 自动完成“信息”文本标记。
        /// </summary>
        void xx_Completed(object sender, EventArgs e)
        {
            this.Select(this.SelectionStart - 67, 7);
        }

        /// <summary>
        /// 自动完成（上屏）。
        /// </summary>
        void TextArea_TextEntered(object sender, TextCompositionEventArgs e)
        {
            if (IsAutoCompletionEnabled == false) return;
            if (CompletionWindow == null) return;

            if (e.Text.Length > 0)
            {
                if (CompletionWindow.CompletionList.SelectedItem == null)
                {
                    CompletionWindow.Close();
                    completionWindow = null;
                    return;
                }

                if (false == char.IsLetterOrDigit(e.Text[0]))
                {
                    if ("=＝+-*^[{/".IndexOf(e.Text[0]) < 0)
                    //不屏蔽这些符号会导致一闪而过，虽然方便，但会难以理解。
                    {
                        // Whenever a non-letter is typed while the completion window is open,
                        // insert the currently selected element.
                        CompletionWindow.CompletionList.RequestInsertion(e);
                    }
                }
            }

            // Do not set e.Handled=true.
            // We still want to insert the character that was typed.
        }

        #endregion

        #region 代码折叠

        void foldingUpdateTimer_Tick(object sender, EventArgs e)
        {
            if (Globals.MainWindow.SupportFolding == false) return;

            if (foldingStrategy != null)
            {
                foldingStrategy.UpdateFoldings(FoldingManager, this.Document);
            }
        }

        DispatcherTimer foldingUpdateTimer = new DispatcherTimer();

        /// <summary>
        /// 提供这个属性是为了便于在选项卡“不在最前”时停止折叠计算，减少消耗。
        /// </summary>
        public DispatcherTimer FoldingUpdateTimer { get { return this.foldingUpdateTimer; } }

        public CustomFoldingManager FoldingManager
        {
            get { return foldingManager; }
        }

        CustomFoldingManager foldingManager = null;
        AbstractFoldingStrategy foldingStrategy = null;

        public AbstractFoldingStrategy FoldingStrategy
        {
            get { return foldingStrategy; }
        }

        #endregion

        // <summary>
        // 以选择区起始位置为准，选择所在的整行文本。
        // </summary>
        public void SelectLine()
        {
            if (this.SelectionStart >= 0 && this.SelectionStart <= this.Document.TextLength)
            {
                var line = this.Document.GetLineByOffset(this.SelectionStart);
                if (line != null)
                {
                    if (this.SelectionStart != line.Offset && this.SelectionLength != line.Length)
                    {
                        this.Select(line.Offset, line.EndOffset - line.Offset);
                    }
                    else
                    {
                        if (line.Length >= 6)
                        {
                            var header = this.Document.GetText(line.Offset, 6);
                            var reg = new Regex(@"　{2,2}(试题|答案|错项|解析)＞{2,}");
                            if (reg.Match(header).Success)
                            {
                                this.Select(line.Offset + 6, line.EndOffset - line.Offset - 6);
                            }
                        }
                    }
                }
            }
        }

        protected override void OnPreviewTextInput(TextCompositionEventArgs e)
        {
            if (e.Text == "`")
            {
                try
                {
                    var leftWrapText = Globals.MainWindow.LeftWrapText;
                    var rightWrapText = Globals.MainWindow.RightWrapText;

                    // 当左右封装字符串都是 ` 时，自动执行弹性封装
                    if (leftWrapText == "`" && rightWrapText == "`")
                    {
                        WrapTextWithAntiQuotes(true);
                        e.Handled = true;
                        return;
                    }

                    // 加上这个，会导致“封装字符串”变成“替换”字符串。
                    //if (leftWrapText == "`" && rightWrapText == "")
                    //{
                    //    base.OnPreviewTextInput(e);
                    //    return;
                    //}

                    var segments = this.TextArea.Selection.Segments;

                    List<ICSharpCode.AvalonEdit.Editing.SelectionSegment> segmentsList = new List<ICSharpCode.AvalonEdit.Editing.SelectionSegment>();
                    foreach (var segment in segments)
                    {
                        segmentsList.Add(segment);
                    }

                    if (segmentsList.Count <= 0)
                    {
                        // 只有插入点，没有选中任何文本，此时不可能是多行。
                        var sel = this.SelectionStart;
                        var selLength = this.SelectionLength;
                        this.BeginChange();
                        this.Document.Insert(sel + selLength, rightWrapText);
                        this.Document.Insert(sel, leftWrapText);
                        this.EndChange();
                        this.Select(sel + leftWrapText.Length, selLength);
                    }
                    else
                    {
                        if (segmentsList.Count == 1 && this.TextArea.Selection.IsMultiline)            // 常规选取方式，选中多行文本。
                        {
                            LMessageBox.ShowWarning("当选中的文本包括换行时，此操作不能执行！");
                            e.Handled = true;
                            return;
                        }

                        // 垂直选取文本，存在 1－n 个片段。
                        this.BeginChange();
                        for (int i = segmentsList.Count - 1; i >= 0; i--)
                        {
                            var segment = segmentsList[i];
                            var sel = segment.StartOffset;
                            var selLength = segment.Length;
                            this.Document.Insert(sel + selLength, rightWrapText);
                            this.Document.Insert(sel, leftWrapText);
                            //this.Select(sel + leftWrapText.Length, selLength);  // 不需要考虑选取区域后移这个问题。AvalonEdit 本身已提供支持。
                        }
                        this.EndChange();
                    }
                    e.Handled = true;
                }
                catch (Exception ex)
                {
                    LMessageBox.ShowWarning(ex.Message);
                }
            }
            else
            {
                if (e.Text.Length == 1 && "1234567890".Contains(e.Text) && Globals.MainWindow.WrapByNumber)
                {
                    // 尝试向后选中指定个数的字符，并且用反引号对封装（这里除非输入0，否则不用弹性封装）
                    if (int.TryParse(e.Text, out int number))
                    {
                        if (number <= 0)
                        {
                            WrapTextWithAntiQuotes(true);  // 输入0时弹性封装
                            e.Handled = true;
                            return;
                        }

                        var rightTxt = RightText;
                        if (rightTxt.Length < number)
                        {
                            this.Select(this.SelectionStart, rightTxt.Length);
                        }
                        else
                        {
                            this.Select(this.SelectionStart, number);
                        }
                        WrapTextWithAntiQuotes();
                        e.Handled = true;
                        return;
                    }
                }

                base.OnPreviewTextInput(e);
            }
        }

        #region 交换上下相邻行，有更好的方案，此方案因只支持单行，被废弃
        //internal void SwapToPreviewLine()
        //{
        //    if (this.SelectionLength > 0) return;

        //    var line = this.Document.GetLineByOffset(this.SelectionStart);
        //    if (line.LineNumber <= 1) return;

        //    int caretOffset = this.SelectionStart - line.Offset;

        //    var previewLine = this.Document.GetLineByNumber(line.LineNumber - 1);

        //    string previewText = this.Document.GetText(previewLine.Offset, previewLine.Length);
        //    string thisText = this.Document.GetText(line.Offset, line.Length);

        //    this.Document.Remove(previewLine.Offset, previewLine.Length);
        //    this.Document.Remove(line.Offset, line.Length);

        //    int previewOffset = this.Document.GetLineByNumber(previewLine.LineNumber).Offset;
        //    this.Document.Insert(previewOffset, thisText);

        //    int thisOffset = this.Document.GetLineByNumber(line.LineNumber).Offset;
        //    this.Document.Insert(thisOffset, previewText);

        //    this.Select(previewOffset + caretOffset, 0);
        //}

        //internal void SwapToNextLine()
        //{
        //    if (this.SelectionLength > 0) return;

        //    var line = this.Document.GetLineByOffset(this.SelectionStart);
        //    if (line.LineNumber >= this.Document.LineCount) return;

        //    int caretOffset = this.SelectionStart - line.Offset;

        //    var nextLine = this.Document.GetLineByNumber(line.LineNumber + 1);

        //    string nextText = this.Document.GetText(nextLine.Offset, nextLine.Length);
        //    string thisText = this.Document.GetText(line.Offset, line.Length);

        //    this.Document.Remove(nextLine.Offset, nextLine.Length);
        //    this.Document.Remove(line.Offset, line.Length);

        //    int thisOffset = this.Document.GetLineByNumber(line.LineNumber).Offset;
        //    this.Document.Insert(thisOffset, nextText);

        //    int nextOffset = this.Document.GetLineByNumber(nextLine.LineNumber).Offset;
        //    this.Document.Insert(nextOffset, thisText);

        //    this.Select(nextOffset + caretOffset, 0);
        //} 
        #endregion

        /// <summary>
        /// 获取字符类别：（数字或英文字母（含半角空格、半角 +-_=[]{};:'\",.<>/?~!@#$%^&*() 等标点）:0，汉字:1，其它:2）。
        /// 半角标点与字母、数字算同类，这是为了方便选取某些类似： Ctrl+X  fontweight-weight: xxx;  kite's 之类的文本片段。
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        private int GetCharClassValue(char c)
        {
            if (" +-_=[]{};:'\",.<>/?~!@#$%^&*()".IndexOf(c) >= 0 || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) return 0;
            if (c >= 0x4e00 && c <= 0x9fbb) return 1;
            return 2;
        }

        /// <summary>
        /// 用一对反引号包围选定的文本片段（不能跨行）。
        /// </summary>
        public void WrapTextWithAntiQuotes(bool autoWrapWithClass = false)
        {
            // 先考虑按字符类别（1.字母数字、2.汉字、3.标点）来进行封装
            // 这种情形无须事先选中——光标停留在这些字符片段的相应位置（开头、中间、结尾）即可
            // 这个功能会自动根据类别选中相信字符并进行封装（如果在两类字符的边界处，则1.类优先）
            if (autoWrapWithClass && this.SelectionLength <= 0)  // 已经选中字符，智能包围不起任用（因为选中的字符可能并非同类字符）。
            {
                // 智能包围：用反引号对自动探测当前插入点位置左右同类字符并尝试用反引号对包围。
                // 字符类别：数字或字母或半角空格或半角逗号、汉字。
                // 数字或字母优先，其后汉字（因为汉字没有空格，学学是一个整句，这个功能用得少）。

                var leftTxt = LeftText;
                var rightTxt = RightText;

                // 先判断左、右两个字符的类别。
                char? leftChar; char? rightChar;
                if (string.IsNullOrWhiteSpace(leftTxt))
                {
                    leftChar = null;
                }
                else leftChar = leftTxt[leftTxt.Length - 1];

                if (string.IsNullOrWhiteSpace(rightTxt))
                {
                    rightChar = null;
                }
                else rightChar = rightTxt[0];

                if (leftChar != null || rightChar != null)
                {
                    var oldSelOffset = this.SelectionStart;
                    if ((leftChar == null || GetCharClassValue(leftChar.Value) > 1) && rightChar != null && GetCharClassValue(rightChar.Value) < 2)
                    {
                        // 向右找同类字符
                        // 类别由右侧第一个字符的类别决定
                        WrapToRight(leftChar, rightChar, rightTxt, oldSelOffset);
                    }
                    else if (leftChar != null && (rightChar == null || GetCharClassValue(rightChar.Value) > 1) && GetCharClassValue(leftChar.Value) < 2)
                    {
                        // 向左找同类字符
                        // 类别由左侧最后一个字符的类别决定
                        WrapToLeft(leftChar, rightChar, leftTxt, oldSelOffset);
                    }
                    else if (leftChar != null && rightChar != null)    // 均不为 null
                    {
                        // 向左右找同类字符
                        var leftClass = GetCharClassValue(leftChar.Value);
                        var rightClass = GetCharClassValue(rightChar.Value);

                        if (leftClass == 1 && rightClass == 0)
                        {
                            // 向右找
                            WrapToRight(leftChar, rightChar, rightTxt, oldSelOffset);
                        }
                        else if (leftClass == 0 && rightClass == 1)
                        {
                            // 向左找
                            WrapToLeft(leftChar, rightChar, leftTxt, oldSelOffset);
                        }
                        else if (leftClass == rightClass && leftClass < 2 && rightClass < 2)
                        {
                            // 左右均不为 2
                            for (int ri = 0; ri < rightTxt.Length; ri++)
                            {
                                var c = rightTxt[ri];
                                if (GetCharClassValue(c) != leftClass)
                                {
                                    this.Select(oldSelOffset + ri, 0);
                                    leftTxt = LeftText;
                                    rightTxt = RightText;
                                    oldSelOffset += ri;
                                    if (string.IsNullOrWhiteSpace(leftTxt))
                                    {
                                        leftChar = null;
                                    }
                                    else leftChar = leftTxt[leftTxt.Length - 1];

                                    if (string.IsNullOrWhiteSpace(rightTxt))
                                    {
                                        rightChar = null;
                                    }
                                    else rightChar = rightTxt[0];
                                    WrapToLeft(leftChar, rightChar, leftTxt, oldSelOffset);
                                    return;
                                }
                            }

                            this.Select(oldSelOffset + rightTxt.Length, 0);
                            leftTxt = LeftText;
                            rightTxt = RightText;
                            oldSelOffset += rightTxt.Length;
                            if (string.IsNullOrWhiteSpace(leftTxt))
                            {
                                leftChar = null;
                            }
                            else leftChar = leftTxt[leftTxt.Length - 1];

                            if (string.IsNullOrWhiteSpace(rightTxt))
                            {
                                rightChar = null;
                            }
                            else rightChar = rightTxt[0];
                            WrapToLeft(leftChar, rightChar, leftTxt, oldSelOffset);
                            return;
                        }
                    }
                    return;
                }  // else {...} 继续执行普通包围
            }

            // 再考虑按一般情形封装
            try
            {
                var startLine = this.Document.GetLineByOffset(this.SelectionStart);
                var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
                if (startLine.LineNumber != endLine.LineNumber) return;
                var leftText = this.LeftText;
                var rightText = this.RightText;

                var oldSelLength = this.SelectionLength;

                // 常见全角标点左右的反引号在视觉上没有必要加空格。
                var punctuationMarks = "“”‘’：｛｝（）［］＜＞｜－＝—＋～·；、！…（）—，。？〖〗【】《》‖＂＇｀〃「」『』．…·¨";

                if (leftText.EndsWith(" ") == false && leftText.Length > 0 && punctuationMarks.Contains(leftText[leftText.Length - 1].ToString()) == false)
                {
                    if (rightText.StartsWith(" ") == false && rightText.Length > 0 && punctuationMarks.Contains(rightText[0].ToString()) == false)
                    {
                        this.SelectedText = " `" + this.SelectedText + "` ";
                    }
                    else
                    {
                        this.SelectedText = " `" + this.SelectedText + "`";
                    }
                    this.Select(this.SelectionStart + 2, oldSelLength);
                }
                else
                {
                    if (rightText.StartsWith(" ") == false && rightText.Length > 0 && punctuationMarks.Contains(rightText[0].ToString()) == false)
                    {
                        this.SelectedText = "`" + this.SelectedText + "` ";
                    }
                    else
                    {
                        this.SelectedText = "`" + this.SelectedText + "`";
                    }
                    this.Select(this.SelectionStart + 1, oldSelLength);
                }

                if (Globals.MainWindow.AutoSwitchInputMethod)
                {
                    Globals.SwitchInputMethod(false);
                }
            }
            catch (Exception ex)
            {
                LMessageBox.ShowWarning(ex.Message);
            }

            // 自动提取的内部方法
            void WrapToRight(char? leftChar, char? rightChar, string rightTxt, int oldSelOffset)
            {
                try
                {
                    var rightClass = GetCharClassValue(rightChar.Value);
                    var breakIndex = 1;
                    for (var i = 1; i < rightTxt.Length; i++)
                    {
                        if (GetCharClassValue(rightTxt[i]) != rightClass && rightTxt[i] != ' ')
                        {
                            break;
                        }
                        breakIndex = i;
                    }

                    var oldSelText = this.GetText(oldSelOffset, breakIndex + 1);
                    string newSelText;
                    if (breakIndex + 1 >= rightTxt.Length || GetCharClassValue(rightTxt[breakIndex + 1]) == 2)
                    {
                        if (leftChar.HasValue && GetCharClassValue(leftChar.Value) == 1)
                        {
                            newSelText = " `" + oldSelText.Trim(new char[] { ' ', '　', '\t' }) + "`";
                            this.Replace(oldSelOffset, breakIndex + 1, newSelText);
                            this.Select(oldSelOffset + 2, oldSelText.Length);
                        }
                        else
                        {
                            newSelText = "`" + oldSelText.Trim(new char[] { ' ', '　', '\t' }) + "`";
                            this.Replace(oldSelOffset, breakIndex + 1, newSelText);
                            this.Select(oldSelOffset + 1, oldSelText.Length);
                        }
                    }
                    else
                    {
                        if (leftChar.HasValue && GetCharClassValue(leftChar.Value) == 1)
                        {
                            newSelText = " `" + oldSelText.Trim(new char[] { ' ', '　', '\t' }) + "` ";
                            this.Replace(oldSelOffset, breakIndex + 1, newSelText);
                            this.Select(oldSelOffset + 3, oldSelText.Length);
                        }
                        else
                        {
                            newSelText = "`" + oldSelText.Trim(new char[] { ' ', '　', '\t' }) + "` ";
                            this.Replace(oldSelOffset, breakIndex + 1, newSelText);
                            this.Select(oldSelOffset + 2, oldSelText.Length);
                        }
                    }
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message);
                }
            }

            // 自动提取的内部方法
            void WrapToLeft(char? leftChar, char? rightChar, string leftTxt, int oldSelOffset)
            {
                try
                {
                    var leftClass = GetCharClassValue(leftChar.Value);
                    var breakIndex = leftTxt.Length - 2;
                    var breakCount = 0;
                    for (var i = leftTxt.Length - 2; i >= 0; i--)
                    {
                        breakIndex = i;
                        if (GetCharClassValue(leftTxt[i]) != leftClass && leftTxt[i] != ' ')
                        {
                            break;
                        }
                        else
                        {
                            breakCount++;
                        }
                    }

                    var newSelOffset = oldSelOffset - breakCount - 1;
                    var oldSelText = this.GetText(newSelOffset, breakCount + 1);

                    var replaceCount = breakCount + 1;

                    // 这里针对 :: 开头的行要作特殊处理：开头这两个字符不应被选中
                    if (oldSelText.StartsWith("::"))
                    {
                        replaceCount = breakCount - 1;   // 其实是 +1 再 -2
                        var line = this.Document.GetLineByOffset(oldSelOffset);
                        if (newSelOffset == line.Offset)
                        {
                            newSelOffset += 2;
                            oldSelText = this.GetText(newSelOffset, replaceCount);
                        }
                    }
                    string newSelText;
                    if (replaceCount == leftTxt.Length || GetCharClassValue(leftTxt[breakIndex]) == 2)
                    {
                        if (rightChar.HasValue && GetCharClassValue(rightChar.Value) == 1)
                        {
                            newSelText = "`" + oldSelText.Trim(new char[] { ' ', '　', '\t' }) + "` ";
                            this.Replace(newSelOffset, replaceCount, newSelText);
                            this.Select(newSelOffset + 1, oldSelText.Length);
                        }
                        else
                        {
                            newSelText = "`" + oldSelText.Trim(new char[] { ' ', '　', '\t' }) + "`";
                            this.Replace(newSelOffset, replaceCount, newSelText);
                            this.Select(newSelOffset + 1, oldSelText.Length);
                        }
                    }
                    else
                    {
                        if (leftChar.HasValue && GetCharClassValue(leftChar.Value) == 1)
                        {
                            newSelText = " `" + oldSelText.Trim(new char[] { ' ', '　', '\t' }) + "` ";
                            this.Replace(newSelOffset, replaceCount, newSelText);
                            this.Select(newSelOffset + 2, oldSelText.Length);
                        }
                        else
                        {
                            newSelText = " `" + oldSelText.Trim(new char[] { ' ', '　', '\t' }) + "`";
                            this.Replace(newSelOffset, replaceCount, newSelText);
                            this.Select(newSelOffset + 2, oldSelText.Length);
                        }
                    }
                }
                catch (Exception ex)
                {
                    LMessageBox.Show(ex.Message);
                }
            }
        }

        /// <summary>
        /// 用一对引号包围选定的文本片段（不能跨行）。
        /// </summary>
        public void WrapTextWithQuotes(bool isShift)
        {
            var startLine = this.Document.GetLineByOffset(this.SelectionStart);
            var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            if (startLine.LineNumber != endLine.LineNumber) return;

            if (this.SelectionLength > 0)
            {
                var oldSelLength = this.SelectionLength;

                if (isShift)
                {
                    this.SelectedText = "“" + this.SelectedText + "”";
                }
                else
                {
                    this.SelectedText = "‘" + this.SelectedText + "’";
                }
                this.Select(this.SelectionStart + 1, oldSelLength);
            }
            else
            {
                if (isShift)
                {
                    this.SelectedText = "“" + this.SelectedText + "”";
                }
                else
                {
                    this.SelectedText = "‘" + this.SelectedText + "’";
                }
                this.Select(this.SelectionStart + 1, 0);
            }
        }

        /// <summary>
        /// 用方括号对【】包围选定的文本片段（不能跨行）。
        /// </summary>
        public void WrapTextWithSquareQuotes()
        {
            var startLine = this.Document.GetLineByOffset(this.SelectionStart);
            var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            if (startLine.LineNumber != endLine.LineNumber) return;

            if (this.SelectionLength > 0)
            {
                this.SelectedText = "【" + this.SelectedText + "】";
            }
            else
            {
                this.SelectedText = "【" + this.SelectedText + "】";
                this.Select(this.SelectionStart + 1, 0);
            }
        }

        /// <summary>
        /// 尝试根据试题文本判断其中有几个填空项，然后根据这些填空项自动插入一些“答案＞＞”“错项＞＞”“解析＞＞”之类的标签。
        /// </summary>
        public void InsertChoiceQuestionTags()
        {
            //选择区域所在的行都选中
            var startLine = this.Document.GetLineByOffset(this.SelectionStart);
            var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            this.Select(startLine.Offset, endLine.EndOffset - startLine.Offset);

            //要求选中题干文本（全部）
            var text = this.SelectedText.Replace("\r\n", "").Replace("\t", "").Replace(" ", "").Replace("　", "");
            if (text.StartsWith("试题＞＞") == false)
            {
                LMessageBox.Show("请选中要生成标签的试题文本，这些文本中应至少有一个用方括弧对标记出来的填充项。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            int fillblankCount = 0;
            StringBuilder errorMsg = new StringBuilder();

            if (QuestionValidateManager.ValidatePairOfBrackets(text, ref fillblankCount, true, errorMsg) == false)
            {
                LMessageBox.Show("选中的文本格式存在问题，无法自动生成标签集。错误信息如下：\r\n" + errorMsg.ToString(),
                   Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            char[] splitterLeft = new char[1];
            splitterLeft[0] = '【';

            char[] splitterRight = new char[1];
            splitterRight[0] = '】';

            string[] arrayLeft = text.Split(splitterLeft, StringSplitOptions.RemoveEmptyEntries);

            List<string> answers = new List<string>();

            foreach (string s in arrayLeft)
            {
                if (s.Contains("】") == false) continue;

                int index = s.IndexOf('】');
                if (index < 0) continue;

                string answer = s.Substring(0, index);
                answers.Add(answer);
            }

            StringBuilder tagsText = new StringBuilder();

            int fstAnswerLength = -1;

            tagsText.Append("\r\n\t\t\t\r\n");
            foreach (var s in answers)
            {
                if (fstAnswerLength < 0)
                {
                    fstAnswerLength = s.Length;
                }

                tagsText.Append("　　答案＞＞" + s + "\r\n");
                tagsText.Append("　　解析＞＞\r\n\t\t\t\r\n");

                //默认三个错项。
                tagsText.Append("　　错项＞＞\r\n");
                tagsText.Append("　　解析＞＞\r\n\t\t\t\r\n");
                tagsText.Append("　　错项＞＞\r\n");
                tagsText.Append("　　解析＞＞\r\n\t\t\t\r\n");
                tagsText.Append("　　错项＞＞\r\n");
                tagsText.Append("　　解析＞＞\r\n\t\t\t\r\n");
            }
            tagsText.Append("〓〓〓〓〓〓\r\n\r\n");//空一行，顶格，方便立即开始编辑新试题

            var line = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength - 1);//这个长度肯定大于0。

            int oldEndOffset = line.EndOffset;

            this.Document.Insert(line.EndOffset, tagsText.ToString());
            this.Select(oldEndOffset + fstAnswerLength + 34, 0);
        }

        /// <summary>
        /// 添加判断题标记。
        /// </summary>
        /// <param name="judgeValue">判断题答案标记。</param>
        public void InsertJudgeQuestionTags(string judgeValue = "正确")
        {
            //选择区域所在的行都选中
            var startLine = this.Document.GetLineByOffset(this.SelectionStart);
            var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
            this.Select(startLine.Offset, endLine.EndOffset - startLine.Offset);

            //要求选中题干文本（全部）
            var text = this.SelectedText.Replace("\r\n", "").Replace("\t", "").Replace(" ", "").Replace("　", "");
            if (text.StartsWith("试题＞＞") == false)
            {
                LMessageBox.Show("请选中要生成标签的试题文本，这些文本中不应包括方括弧【和】，因为方括弧对包括的文本会被当成填充项。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            if (text.Contains("【") || text.Contains("】"))
            {
                LMessageBox.Show("选中的文本格式存在问题，无法自动生成判断题标签集。错误信息如下：\r\n" +
                    "判断题题干中不应包括方括号【和】，被方括号包围的文本会被当成填空项来处理。",
                    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
                return;
            }

            //在末尾加上【正确】或【错误】。
            text += "【" + judgeValue + "】";

            char[] splitterLeft = new char[1];
            splitterLeft[0] = '【';

            char[] splitterRight = new char[1];
            splitterRight[0] = '】';

            string[] arrayLeft = text.Split(splitterLeft, StringSplitOptions.RemoveEmptyEntries);

            List<string> answers = new List<string>();

            foreach (string s in arrayLeft)
            {
                if (s.Contains("】") == false) continue;

                int index = s.IndexOf('】');
                if (index < 0) continue;

                string answer = s.Substring(0, index);
                answers.Add(answer);
            }

            StringBuilder tagsText = new StringBuilder();

            int fstAnswerLength = -1;

            tagsText.Append("【" + judgeValue + "】\r\n\t\t\t\r\n");
            foreach (var s in answers)
            {
                if (fstAnswerLength < 0)
                {
                    fstAnswerLength = s.Length;
                }

                tagsText.Append("　　答案＞＞" + judgeValue + "\r\n");
                tagsText.Append("　　解析＞＞\r\n\t\t\t\r\n");

                //默认三个错项。
                tagsText.Append("　　错项＞＞" + (judgeValue == "正确" ? "错误" : "正确") + "\r\n");
                tagsText.Append("　　解析＞＞\r\n\t\t\t\r\n");
            }
            tagsText.Append("〓〓〓〓〓〓\r\n\r\n");//空一行，顶格，方便立即开始编辑新试题

            var line = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength - 1);//这个长度肯定大于0。

            int oldEndOffset = line.EndOffset;

            this.Document.Insert(line.EndOffset, tagsText.ToString());
            this.Select(oldEndOffset + fstAnswerLength + 25, 0);
        }

        /// <summary>
        /// 格式化 Markdown 文本。
        /// </summary>
        public void Format()
        {
            var selStart = this.SelectionStart;
            var selLength = this.SelectionLength;

            this.Document.Text = CustomMarkdownSupport.FormatMarkdownText(this.Text);

            //如果开启了演讲者模式，格式化后自动折叠特殊折叠块。
            if (Globals.MainWindow.PerspectiveMode == Perspective.EditingAndPresentation)
            {
                this.FoldingManager.FoldSpecialFoldings = true;
                this.FoldingStrategy.UpdateFoldings(this.FoldingManager, this.Document);
                this.FoldingManager.FoldSpecialFoldings = false;
            }

            if (selStart < this.Document.TextLength)
            {
                if (selStart + selLength < this.Document.TextLength)
                {
                    this.Select(selStart, selLength);
                    this.ScrollToLine(this.Document.GetLineByOffset(this.SelectionStart).LineNumber);
                }
                else
                {
                    this.Select(selStart, 0);
                    this.ScrollToLine(this.Document.GetLineByOffset(this.SelectionStart).LineNumber);
                }
            }
            else
            {
                this.Select(this.Document.TextLength, 0);
                this.ScrollToLine(this.Document.GetLineByOffset(this.SelectionStart).LineNumber);
            }
        }

        public void FormatTextTable(bool autoAggregate = false)
        {
            var curLine = CurrentDocumentLine();
            var lineNumber = curLine.LineNumber;
            var curLineText = this.Document.GetText(curLine.Offset, curLine.Length);

            if (CustomMarkdownSupport.IsTreeListTextLine(curLineText))
            {
                //如果当前行是树型列表行，则应格式化当前行前、后可能存在的树型列表行。

                List<TreeListTextLine> allLines = new List<TreeListTextLine>();

                //向前找
                List<TreeListTextLine> preLines = new List<TreeListTextLine>();

                var preLine = curLine.PreviousLine;
                while (preLine != null)
                {
                    var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
                    if (CustomMarkdownSupport.IsTreeListTextLine(preLineText) == false) break;

                    string preHeader, preTail; int preLevel;
                    if (IsTreeListLine(preLineText, out preHeader, out preTail, out preLevel))
                    {
                        preLines.Insert(0, new TreeListTextLine()
                        {
                            Line = preLine,
                            OldText = preLineText,
                            NewText = "",
                            //HeaderTextArray = preHeader.ToCharArray(),
                            TailText = preTail,
                            OldLevel = preLevel,
                            NewLevel = preLevel,   //之前的行层级不变
                        });
                    }

                    preLine = preLine.PreviousLine;
                }

                allLines.AddRange(preLines);

                string curHeader, curTail; int curLevel;
                if (IsTreeListLine(curLineText, out curHeader, out curTail, out curLevel))
                {
                    allLines.Add(new TreeListTextLine()
                    {
                        Line = curLine,
                        OldText = curLineText,
                        TailText = curTail,
                        OldLevel = curLevel,
                        NewLevel = curLevel,
                        NewText = "",
                    });
                }

                //向后找
                List<TreeListTextLine> nextLines = new List<TreeListTextLine>();

                var nextLine = curLine.NextLine;
                var preLevelOfNextLines = curLevel;
                while (nextLine != null)
                {
                    var nextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
                    if (CustomMarkdownSupport.IsTreeListTextLine(nextLineText) == false) break;
                    string nextHeader, nextTail; int nextLevel;
                    if (IsTreeListLine(nextLineText, out nextHeader, out nextTail, out nextLevel))
                    {
                        var nextTreeListTextLine = new TreeListTextLine()
                        {
                            Line = nextLine,
                            //HeaderTextArray = nextHeader.ToCharArray(),
                            NewText = "",
                            OldText = nextLineText,
                            TailText = nextTail,
                            OldLevel = nextLevel,
                        };

                        if (nextTreeListTextLine.OldLevel > preLevelOfNextLines + 1)
                        {
                            nextTreeListTextLine.NewLevel = preLevelOfNextLines + 1;
                        }
                        else nextTreeListTextLine.NewLevel = nextTreeListTextLine.OldLevel;//不变

                        preLevelOfNextLines = nextTreeListTextLine.NewLevel;

                        nextLines.Add(nextTreeListTextLine);
                        nextLine = nextLine.NextLine;
                    }
                }

                allLines.AddRange(nextLines);

                var oldSelStart = this.SelectionStart;
                FormatAllTreeLines(allLines, null, autoAggregate);
                if (oldSelStart < this.Document.TextLength)
                {
                    this.Select(oldSelStart, 0);
                }

                return;
            }
            else
            {
                // 格式化 ；****************** 这样的注释块
                var markReg = new Regex(@"[*＊+＋\-－]$");
                var markMatch = markReg.Match(curLineText);
                if (CustomMarkdownSupport.IsCommentLine(curLineText) && markMatch.Success)
                {
                    //向前找
                    List<DocumentLine> allLines = new List<DocumentLine>();
                    List<string> commentLines = new List<string>();

                    var preLine = curLine.PreviousLine;
                    while (preLine != null)
                    {
                        var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
                        if (Regex.Match(preLineText, @"^[；;].*[*＊+＋\-－]$").Success == false) break;

                        allLines.Insert(0, preLine);
                        commentLines.Insert(0, preLineText);
                        preLine = preLine.PreviousLine;
                    }

                    allLines.Add(curLine);
                    commentLines.Add(curLineText);

                    //向后找
                    var nextLine = curLine.NextLine;
                    while (nextLine != null)
                    {
                        var nextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
                        if (Regex.Match(nextLineText, @"^[；;].*[*＊+＋\-－]$").Success == false) break;

                        allLines.Add(nextLine);
                        commentLines.Add(nextLineText);
                        nextLine = nextLine.NextLine;
                    }

                    FormatStarsCommentLines(allLines, commentLines, markMatch.Value);
                    this.Select(this.Document.GetLineByNumber(lineNumber).EndOffset - 2, 0);
                }
                else if (curLineText.Contains("｜") || curLineText.Contains("|"))
                {
                    //这种情况下格式化为二维文字表。
                    Format2DTextTable();
                }
                else
                {
                    //如果当前行是按Shift+Backspace取消的，则要更新当前行上、下可能存在的树型文字表
                    //这种情况要分别格式化成两个树型文字表。

                    //向前找
                    List<TreeListTextLine> preLines = new List<TreeListTextLine>();

                    var preLine = curLine.PreviousLine;
                    while (preLine != null)
                    {
                        var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
                        if (CustomMarkdownSupport.IsTreeListTextLine(preLineText) == false) break;

                        string preHeader, preTail; int preLevel;
                        if (IsTreeListLine(preLineText, out preHeader, out preTail, out preLevel))
                        {
                            preLines.Insert(0, new TreeListTextLine()
                            {
                                Line = preLine,
                                OldText = preLineText,
                                NewText = "",
                                //HeaderTextArray = preHeader.ToCharArray(),
                                TailText = preTail,
                                OldLevel = preLevel,
                                NewLevel = preLevel,   //之前的行层级不变
                            });
                        }

                        preLine = preLine.PreviousLine;
                    }

                    FormatAllTreeLines(preLines, null, autoAggregate);

                    //向后找
                    List<TreeListTextLine> nextLines = new List<TreeListTextLine>();

                    var nextLine = curLine.NextLine;
                    while (nextLine != null)
                    {
                        var nextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
                        if (CustomMarkdownSupport.IsTreeListTextLine(nextLineText) == false) break;
                        string nextHeader, nextTail; int nextLevel;
                        if (IsTreeListLine(nextLineText, out nextHeader, out nextTail, out nextLevel))
                        {
                            var nextTreeListTextLine = new TreeListTextLine()
                            {
                                Line = nextLine,
                                //HeaderTextArray = nextHeader.ToCharArray(),
                                NewText = "",
                                OldText = nextLineText,
                                TailText = nextTail,
                                OldLevel = nextLevel - 1,
                            };
                            nextLines.Add(nextTreeListTextLine);
                        }

                        nextLine = nextLine.NextLine;
                    }

                    FormatAllTreeLines(nextLines, null, autoAggregate);
                }
            }
        }

        private void FormatStarsCommentLines(List<DocumentLine> allLines, List<string> commentLines, string mark)
        {
            if (allLines == null || allLines.Count < 1) return;
            if (commentLines == null || commentLines.Count < 1) return;

            var sb = new StringBuilder();
            var maxLength = 0;
            for (int i = 0; i < commentLines.Count; i++)
            {
                if (Regex.Match(commentLines[i], @"^[；;][ 　\t]+[*＊+＋\-－]$").Success)
                {
                    commentLines[i] = "===<<blank line>>===";
                    continue;
                }

                if (Regex.Match(commentLines[i], @"^[；;][*＊+＋\-－]+$").Success)
                {
                    commentLines[i] = "===<<wrap line>>===";
                    continue;
                }

                var cl = commentLines[i].Trim(new char[] { '；', '*', '+', ';', '＊', '＋', '-', '－', ' ', '　', '\t' });
                commentLines[i] = cl;
                maxLength = Math.Max(纯文本处理器.取文本宽度(cl), maxLength) + 2;
            }

            switch (mark)
            {
                case "＊":
                    mark = "*"; break;
                case "－":
                    mark = "-"; break;
                case "＋":
                    mark = "+"; break;
            }

            for (int i = 0; i < commentLines.Count; i++)
            {
                var cl = commentLines[i];
                if (cl == "===<<blank line>>===")
                {
                    sb.AppendLine("；" + new string(' ', maxLength) + mark.ToString());
                    continue;
                }
                if (cl == "===<<wrap line>>===")
                {
                    sb.AppendLine("；" + new string(mark[0], maxLength) + mark.ToString());
                    continue;
                }

                if (cl.Length < maxLength)
                {
                    cl += new string(' ', maxLength - 纯文本处理器.取文本宽度(cl));
                }
                sb.Append("；" + cl + mark.ToString() + "\r\n");
            }

            var startOffset = allLines[0].Offset;
            var endOffset = allLines[allLines.Count - 1].EndOffset;

            this.Document.Replace(startOffset, endOffset - startOffset, sb.ToString().TrimEnd(new char[] { '\r', '\n' }));
        }

        /// <summary>
        /// 格式化二维文字表。
        /// </summary>
        /// <param name="insertColumnIndex">可以指定文字表的列数。</param>
        private void Format2DTextTable(int? insertColumnIndex = null)
        {
            try
            {
                // 用于格式化向右合并单元格的后缀
                Regex regColSpan = new Regex(@"(?<=([ 　]{1,}))[\-－][\>》〉]{1,}?$");
                var trimChars = new char[] { ' ', '　', '\t', };

                var curLine = this.Document.GetLineByOffset(this.SelectionStart);
                var curLineNumber = curLine.LineNumber;
                int selStartOffset = this.SelectionStart - curLine.Offset;

                this.Document.UndoStack.StartUndoGroup();

                int startOffset;
                int endOffset;
                string commentText;
                var srcTableText = FindSourceTableText(out startOffset, out endOffset, out commentText);

                if (string.IsNullOrEmpty(srcTableText)) return;

                var lines = new List<TableLine>();

                var srcLines = srcTableText.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
                foreach (string s in srcLines)
                {
                    if (IsTableCaptionSplittor(s)) continue;//忽略“表格格式化”功能自动添加的标题与标题分割行。
                    if (s.StartsWith(";") || s.StartsWith("；")) continue;//忽略备注

                    if (s.EndsWith("｜^") || s.EndsWith("|^"))
                    {
                        lines.Add(new TableLine() { LineText = s.Substring(0, s.Length - 1), Type = TableLineType.MergeLine, });
                    }
                    else
                    {
                        lines.Add(new TableLine() { LineText = s, Type = TableLineType.Normal, });
                    }
                }

                if (lines.Count <= 0) return;

                var cellSplittor = new char[] { '｜' };
                var maxColumn = 0;

                var lineArrays = new List<TableCellArray>();
                string[] columnDefinitionArray = null;

                var columnDefinitionLineIndex = -1;
                var removedColumnDefinitionLinesCount = 0;
                var columnDefinitionLineFinded = false;
                for (int i = 0; i < lines.Count; i++)
                {
                    var text = lines[i].LineText;
                    text = text.Replace("|", "｜").Trim();
                    if (text.StartsWith("｜", StringComparison.CurrentCulture)) text = text.Substring(1);
                    if (text.EndsWith("｜", StringComparison.CurrentCulture)) text = text.Substring(0, text.Length - 1);

                    var cells = text.Split(cellSplittor, StringSplitOptions.None);
                    for (int j = 0; j < cells.Length; j++)
                    {
                        cells[j] = cells[j].Trim(trimChars);
                        var matchColSpan = regColSpan.Match(cells[j]);
                        if (matchColSpan.Success)
                        {
                            cells[j] = cells[j].Substring(0, cells[j].Length - matchColSpan.Length).Trim(trimChars) + " -" + new string('>', matchColSpan.Length - 1);
                        }
                    }

                    if (CustomMarkdownSupport.IsColumnAlignmentDefinitionLine("｜" + text + "｜"))
                    {
                        columnDefinitionArray = cells;
                        if (columnDefinitionLineFinded == false)
                        {
                            columnDefinitionLineIndex = i;
                            columnDefinitionLineFinded = true;
                        }
                        removedColumnDefinitionLinesCount++;
                    }
                    else
                    {
                        lineArrays.Add(new TableCellArray() { Cells = cells, Type = lines[i].Type, });
                    }

                    lines[i].LineText = text;
                    maxColumn = Math.Max(cells.Length, maxColumn);
                }

                var columnDefinitions = new List<TableColumnDefinition>();
                for (int i = 0; i < maxColumn; i++)
                {
                    columnDefinitions.Add(new TableColumnDefinition());
                }

                //if (columnDefinitions.Count <= 0 || lineArrays.Count <= 0)
                //{
                //    //LMessageBox.Show("插入附近似乎没有可以格式化的表格！", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                //    //return;

                //    for (int i = 0; i < maxColumn; i++)
                //    {
                //        columnDefinitions.Add(new TableColumnDefinition());
                //    }
                //}

                #region 取各列对齐方式定义
                bool hascolumndefinitions = false;
                if (columnDefinitionArray != null && columnDefinitionArray.Length > 0)
                {
                    hascolumndefinitions = true;
                    for (int i = 0; i < maxColumn; i++)
                    {
                        if (i < columnDefinitionArray.Length)
                        {
                            columnDefinitionArray[i] = columnDefinitionArray[i].Replace("：", ":").Trim();

                            if (columnDefinitionArray[i].Contains("^"))
                            {
                                columnDefinitions[i].StartNumber = 0;
                            }

                            if (columnDefinitionArray[i].StartsWith(":", StringComparison.CurrentCulture) ||
                                columnDefinitionArray[i].StartsWith("^:", StringComparison.CurrentCulture))
                            {
                                columnDefinitions[i].Align =
                                    (columnDefinitionArray[i].EndsWith(":", StringComparison.CurrentCulture) ||
                                    columnDefinitionArray[i].EndsWith(":^", StringComparison.CurrentCulture)) ?
                                    ColumnAlignment.CENTER : ColumnAlignment.LEFT;
                            }
                            else if (columnDefinitionArray[i].EndsWith(":", StringComparison.CurrentCulture) ||
                                columnDefinitionArray[i].EndsWith(":^", StringComparison.CurrentCulture))
                            {
                                columnDefinitions[i].Align = ColumnAlignment.RIGHT;
                            }
                            else
                            {
                                columnDefinitions[i].Align = ColumnAlignment.LEFT;
                            }
                        }
                        else
                        {
                            columnDefinitions[i].Align = ColumnAlignment.LEFT;
                        }
                    }
                }
                #endregion

                #region 取各列最大字符宽度
                for (int i = 0; i < lineArrays.Count; i++)
                {
                    var cells = lineArrays[i];

                    for (int j = 0; j < maxColumn; j++)
                    {
                        if (j == 0 && i == 0 && cells.Cells.Length == 1 && hascolumndefinitions)
                        {
                            //第一列需要考虑标题的长度，不能把表格标题的长度作为第一列所需要的最大宽度
                            continue;
                        }

                        if (j < cells.Cells.Length)
                        {
                            columnDefinitions[j].MaxTextWidth = Math.Max(Utils.Text.GetTextWidth2(cells.Cells[j]), columnDefinitions[j].MaxTextWidth);
                        }
                    }

                    for (int j = 0; j < columnDefinitions.Count; j++)
                    {
                        if (columnDefinitions[j].MaxTextWidth % 2 == 1)
                        {
                            columnDefinitions[j].MaxTextWidth += 1;
                        }
                    }
                }
                #endregion

                var sb = new StringBuilder();

                if (columnDefinitionLineIndex >= 0)
                {
                    //标题（可能不存在）
                    var headerStartIndex = 0;
                    if (lineArrays[0].Cells.Length == 1 && columnDefinitions.Count > 0)
                    {
                        headerStartIndex = 1;//表头行从索引值1开始算。

                        //如果第一行只有一个成员，算表的标题。
                        sb.Append("｜");
                        sb.Append(BuildTableCaptionText(lineArrays[0].Cells[0], columnDefinitions, insertColumnIndex));
                        sb.Append("｜\r\n");
                    }

                    var sbHeader = new StringBuilder();
                    //表头（可能不止一行），在
                    //removedColumnDefinitionLinesCount存在的意义是什么？用户可能编写超过一行的列定义行
                    var headerLinesCount = 0;
                    for (int lineIndex = headerStartIndex; lineIndex < columnDefinitionLineIndex; lineIndex++)
                    {
                        //表格标题、表头均居中对齐
                        var cells = lineArrays[lineIndex];
                        sbHeader.Append("｜");
                        for (int i = 0; i < maxColumn; i++)
                        {
                            int maxTextLength = columnDefinitions[i].MaxTextWidth;
                            if (i < cells.Cells.Length)
                            {
                                sbHeader.Append(BuildCellText(cells.Cells[i], maxTextLength, ColumnAlignment.CENTER));
                                if (cells.Cells[i].Trim() == "序号" || cells.Cells[i].Trim().ToLower() == "no.")
                                {
                                    columnDefinitions[i].StartNumber = 0;
                                }
                            }
                            else
                            {
                                sbHeader.Append(BuildCellText(null, maxTextLength, ColumnAlignment.CENTER));
                            }

                            if (insertColumnIndex != null && insertColumnIndex.HasValue && insertColumnIndex.Value == i)
                            {
                                sbHeader.Append("｜   ｜");
                            }
                            else
                            {
                                sbHeader.Append("｜");
                            }
                        }
                        sbHeader.Append("\r\n");
                        headerLinesCount++;
                    }

                    if (headerLinesCount > 0 && lineArrays[0].Cells.Length == 1 && columnDefinitions.Count > 0)
                    {
                        sb.Append("｜");
                        sb.Append(BuildTableCaptionSplittorLine(columnDefinitions, insertColumnIndex));
                        sb.Append("｜\r\n");
                    }

                    sb.Append(sbHeader);

                    //列定义行
                    if (columnDefinitionLineIndex >= 0)
                    {
                        sb.Append("｜");
                        for (int i = 0; i < maxColumn; i++)
                        {
                            int maxTextWidth = columnDefinitions[i].MaxTextWidth;
                            sb.Append(GetColumnDefinitionText(maxTextWidth, columnDefinitions[i].Align, columnDefinitions[i].StartNumber == 0));

                            if (insertColumnIndex != null && insertColumnIndex.HasValue && insertColumnIndex.Value == i)
                            {
                                sb.Append("｜---｜");
                            }
                            else
                            {
                                sb.Append("｜");
                            }
                        }
                        sb.Append("\r\n");
                    }

                    //表体（可能不存在）
                    for (int lineIndex = columnDefinitionLineIndex; lineIndex < lineArrays.Count; lineIndex++)
                    {
                        var cells = lineArrays[lineIndex];
                        sb.Append("｜");
                        for (int i = 0; i < maxColumn; i++)
                        {
                            int maxTextLength = columnDefinitions[i].MaxTextWidth;
                            if (i < cells.Cells.Length)
                            {
                                if (columnDefinitions[i].StartNumber.HasValue)
                                {
                                    columnDefinitions[i].StartNumber += 1;
                                    sb.Append(BuildCellText(columnDefinitions[i].StartNumber.Value.ToString("D2"), maxTextLength, columnDefinitions[i].Align));
                                }
                                else
                                {
                                    sb.Append(BuildCellText(cells.Cells[i], maxTextLength, columnDefinitions[i].Align));
                                }
                            }
                            else
                            {
                                sb.Append(BuildCellText(null, maxTextLength, columnDefinitions[i].Align));
                            }

                            if (insertColumnIndex != null && insertColumnIndex.HasValue && insertColumnIndex.Value == i)
                            {
                                sb.Append("｜   ｜");
                            }
                            else
                            {
                                sb.Append("｜");
                            }
                        }

                        if (lineArrays[lineIndex].Type == TableLineType.MergeLine)
                        {
                            sb.Append("^");
                        }
                        sb.Append("\r\n");
                    }

                    if (string.IsNullOrEmpty(commentText) == false) sb.Append("\r\n");
                }
                else
                {
                    //LMessageBox.Show("在附近文本中没有找到【表示表格中各列对齐方式】的那一行文本，无法格式化！\r\n" +
                    //"　　该文本一般会类似下面这行：\r\n\r\n" +
                    //"　　　　|:------|:------:|------:|\r\n\r\n" +
                    //"　　这行文本分别表示对应的列：【左对齐】、【居中对齐】、【右对齐】。",
                    //    Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
                    //return;

                    //如果没有“列对齐定义”这一行，就将所有列都视为表体列好了。

                    for (int lineIndex = 0; lineIndex < lineArrays.Count; lineIndex++)
                    {
                        var cells = lineArrays[lineIndex];
                        sb.Append("｜");
                        for (int i = 0; i < maxColumn; i++)
                        {
                            int maxTextLength = columnDefinitions[i].MaxTextWidth;
                            if (i < cells.Cells.Length)
                            {
                                sb.Append(BuildCellText(cells.Cells[i], maxTextLength, columnDefinitions[i].Align));
                            }
                            else
                            {
                                sb.Append(BuildCellText(null, maxTextLength, columnDefinitions[i].Align));
                            }

                            if (insertColumnIndex != null && insertColumnIndex.HasValue && insertColumnIndex.Value == i)
                            {
                                sb.Append("｜   ｜");
                            }
                            else
                            {
                                sb.Append("｜");
                            }
                        }

                        if (lineArrays[lineIndex].Type == TableLineType.MergeLine)
                        {
                            sb.Append("^");
                        }
                        sb.Append("\r\n");
                    }

                    if (string.IsNullOrEmpty(commentText) == false) sb.Append("\r\n");
                }

                var result = sb.ToString();
                if (srcTableText.EndsWith("\r\n") == false && result.EndsWith("\r\n"))
                {
                    result = result.Substring(0, result.Length - 2);
                }

                this.Document.Replace(startOffset, endOffset - startOffset, result + (commentText ?? ""));

                this.Document.UndoStack.EndUndoGroup();

                if (curLineNumber <= this.Document.LineCount)
                {
                    var cl = this.Document.GetLineByNumber(curLineNumber);
                    if (cl != null)
                    {
                        var selStart = cl.Offset + Math.Min(selStartOffset, cl.Length);
                        this.Select(selStart, 0);
                    }
                }
            }
            catch (Exception ex)
            {
                LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        /// <summary>
        /// 注意：如果标题文本行是当前文档最后一行，此方法无效！！
        /// </summary>
        /// <returns></returns>
        public string 取文档标题_实时()
        {
            var title = this.取文本片段("\r\n%", "\r\n").Trim();
            if (string.IsNullOrWhiteSpace(title))
            {
                return "";
            }

            return title;
        }

        public string 取标题文本_实时()
        {
            return 取文档标题_实时();
        }

        /// <summary>
        /// 注意：如果副标题文本行是当前文档最后一行，此方法无效！！
        /// </summary>
        /// <returns></returns>
        public string 取文档副标题_实时()
        {
            var sub_title = this.取文本片段("\r\n%[－\\-—]{2,}", "\r\n").Trim();
            if (string.IsNullOrWhiteSpace(sub_title))
            {
                return "";
            }

            return sub_title;
        }

        public string 取副标题文本_实时()
        {
            return 取文档副标题_实时();
        }

        /// <summary>
        /// 生成文字表标题分割行文本。
        /// </summary>
        /// <param name="columnDefinitions">列定义集。</param>
        /// <param name="insertColumnIndex">列数。</param>
        private string BuildTableCaptionSplittorLine(List<TableColumnDefinition> columnDefinitions, int? insertColumnIndex)
        {
            if (columnDefinitions == null || columnDefinitions.Count <= 0) return string.Empty;

            var length = 0;
            foreach (var cd in columnDefinitions)
            {
                length += cd.MaxTextWidth;
                length += 2;
            }

            if (length > 2) length -= 2;

            if (insertColumnIndex.HasValue)
            {
                length += 5;////不需要考虑索引值到底是多少，只需要知道需要插入一列即可。一个空列是“   ｜”宽度为5。
            }

            var sb = new StringBuilder();
            for (int i = 0; i < length; i++)
            {
                sb.Append("=");
            }

            return sb.ToString();
        }

        /// <summary>
        /// 生成二维文字表的标题文本行。
        /// </summary>
        /// <param name="source">源文本。</param>
        /// <param name="columnDefinitions">列定义集合。</param>
        /// <param name="insertColumnIndex">列数。</param>
        private string BuildTableCaptionText(string source, List<TableColumnDefinition> columnDefinitions, int? insertColumnIndex)
        {
            if (columnDefinitions == null || columnDefinitions.Count <= 0) return source;

            var srcWidth = Utils.Text.GetTextWidth2(source, false, CustomMarkdownSupport.IsColumnAlignmentDefinitionLine(source));
            var sumTextWidth = 0;
            foreach (var cd in columnDefinitions)
            {
                sumTextWidth += cd.MaxTextWidth;
                sumTextWidth += 2;
            }

            if (sumTextWidth > 2) sumTextWidth -= 2;//末尾一个“｜”字符宽度不算。

            if (insertColumnIndex.HasValue)
            {
                sumTextWidth += 5;//不需要考虑索引值到底是多少，只需要知道需要插入一列即可。一个空列是“   ｜”宽度为5。
            }

            //表格标题居中对齐
            var sb = new StringBuilder();
            var offset = sumTextWidth - srcWidth;
            for (int i = 1; i <= offset / 2; i++)
            {
                sb.Append(" ");
            }

            string result;
            if ((int)(offset / 2) * 2 + 1 == offset)
            {
                result = sb.ToString() + (source ?? "") + sb.ToString() + " ";
            }
            else
            {
                result = sb.ToString() + (source ?? "") + sb.ToString();
            }

            return result;
        }

        /// <summary>
        /// 取出列定义文本。
        /// </summary>
        /// <param name="maxLength">最大长度。</param>
        /// <param name="align">列对齐方式。</param>
        /// <param name="withSortMark">是否带“填充序号”标志符（^）。</param>
        /// <returns></returns>
        private string GetColumnDefinitionText(int maxLength, ColumnAlignment align, bool withSortMark)
        {
            if (withSortMark)
            {
                switch (align)
                {
                    case ColumnAlignment.LEFT:
                        {
                            var sb = new StringBuilder();
                            for (int i = 0; i < (maxLength - 2); i++)
                            {
                                sb.Append("-");
                            }
                            return ":" + sb.ToString() + "^";
                        }
                    case ColumnAlignment.RIGHT:
                        {
                            var sb = new StringBuilder();
                            for (int i = 0; i < (maxLength - 2); i++)
                            {
                                sb.Append("-");
                            }
                            return sb.ToString() + "^:";
                        }
                    case ColumnAlignment.CENTER:
                    default:
                        {
                            var sb = new StringBuilder();
                            for (int i = 0; i < maxLength - 3; i++)
                            {
                                sb.Append("-");
                            }
                            return ":" + sb.ToString() + "^:";
                        }
                }
            }
            else
            {
                switch (align)
                {
                    case ColumnAlignment.LEFT:
                        {
                            var sb = new StringBuilder();
                            for (int i = 0; i < (maxLength - 1); i++)
                            {
                                sb.Append("-");
                            }
                            return ":" + sb.ToString();
                        }
                    case ColumnAlignment.RIGHT:
                        {
                            var sb = new StringBuilder();
                            for (int i = 0; i < (maxLength - 1); i++)
                            {
                                sb.Append("-");
                            }
                            return sb.ToString() + ":";
                        }
                    case ColumnAlignment.CENTER:
                    default:
                        {
                            var sb = new StringBuilder();
                            for (int i = 0; i < maxLength - 2; i++)
                            {
                                sb.Append("-");
                            }
                            return ":" + sb.ToString() + ":";
                        }
                }
            }
        }

        /// <summary>
        /// 仅为脚本兼容性保留，已移植到 Utils.Text.cs 文件中。
        /// </summary>
        /// <param name="text"></param>
        /// <param name="trimStartAndEnd"></param>
        /// <returns></returns>
        public static int GetTextWidth(string text, bool trimStartAndEnd = false)
        {
            return Utils.Text.GetTextWidth2(text, trimStartAndEnd);
        }

        /// <summary>
        /// 用半角空格填充，使单元格中文本能够对齐。
        /// </summary>
        /// <param name="source">要填空空格的源文本。</param>
        /// <param name="maxTextWidth">最大宽度。</param>
        /// <param name="align">对齐方式</param>
        private string BuildCellText(string source, int maxTextWidth, ColumnAlignment align)
        {
            var srcWidth = Utils.Text.GetTextWidth2(source, false, false);

            string result;
            switch (align)
            {
                case ColumnAlignment.LEFT:
                    {
                        var sb = new StringBuilder();
                        for (int i = 1; i <= maxTextWidth - srcWidth; i++)
                        {
                            sb.Append(" ");
                        }
                        result = (source ?? "") + sb.ToString();
                        break;
                    }
                case ColumnAlignment.RIGHT:
                    {
                        var sb = new StringBuilder();
                        for (int i = 1; i <= maxTextWidth - srcWidth; i++)
                        {
                            sb.Append(" ");
                        }
                        result = sb.ToString() + (source ?? "");
                        break;
                    }
                //case ColumnDefinition.CENTER:
                default:
                    {
                        var sb = new StringBuilder();
                        var offset = maxTextWidth - srcWidth;
                        for (int i = 1; i <= offset / 2; i++)
                        {
                            sb.Append(" ");
                        }

                        if ((int)(offset / 2) * 2 + 1 == offset)
                        {
                            result = sb.ToString() + (source ?? "") + sb.ToString() + " ";
                            break;
                        }
                        else
                        {
                            result = sb.ToString() + (source ?? "") + sb.ToString();
                            break;
                        }
                    }
            }
            return result;
        }

        /// <summary>
        /// 根据指定文本偏移位置，取附近属于二维文字表的所有文本行。
        /// </summary>
        /// <param name="startOffset">文本偏移位置（头索引）。</param>
        /// <param name="endOffset">文本偏移位置（尾索引）</param>
        /// <param name="commentText">注释文本。</param>
        /// <returns></returns>
        private string FindSourceTableText(out int startOffset, out int endOffset, out string commentText)
        {
            var startSelLine = this.Document.GetLineByOffset(this.SelectionStart);
            var endSelLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);

            if (startSelLine == null || endSelLine == null)
            {
                startOffset = endOffset = -1;
                commentText = null;
                return null;
            }

            if (startSelLine.LineNumber == endSelLine.LineNumber)
            {
                var text = this.Document.GetText(startSelLine.Offset, startSelLine.Length);
                if (string.IsNullOrEmpty(text) || (text.Contains("|") == false && text.Contains("｜") == false))
                {
                    startOffset = endOffset = -1;
                    commentText = null;
                    return null;
                }
            }

            //基本思路：向上、向下找到表格的首、尾两行。
            var commentString = new StringBuilder();

            int i = startSelLine.LineNumber;//LineNumber要比索引大1。索引仍然是从0开始计算的。
            while (i >= 1)
            {
                var line = this.Document.GetLineByNumber(i);
                if (line == null) break;

                var previewLineText = this.Document.GetText(line.Offset, line.Length);

                if (previewLineText.StartsWith(";") || previewLineText.StartsWith("；"))
                {
                    //注释不算
                    commentString.Insert(0, previewLineText + "\r\n");
                    i--;
                    continue;
                }

                if (previewLineText.Contains("|") == false && previewLineText.Contains("｜") == false) break;

                i--;
            }

            int fstLineNumber = i + 1;

            i = endSelLine.LineNumber;
            while (i <= this.Document.LineCount)
            {
                var line = this.Document.GetLineByNumber(i);
                if (line == null) break;

                var nextLineText = this.Document.GetText(line.Offset, line.Length);

                if (nextLineText.StartsWith(";") || nextLineText.StartsWith("；"))
                {
                    //注释不算
                    commentString.Append(nextLineText + "\r\n");
                    i++;
                    continue;
                }

                if (nextLineText.Contains("|") == false && nextLineText.Contains("｜") == false) break;

                i++;
            }

            int lastLineNumber = i - 1;

            startOffset = this.Document.GetLineByNumber(fstLineNumber).Offset;
            endOffset = this.Document.GetLineByNumber(lastLineNumber).EndOffset;
            commentText = commentString.ToString();

            var srcText = this.Document.GetText(startOffset, endOffset - startOffset);

            return srcText;
        }

        /// <summary>
        /// 判断此行文本是否只包含“｜”、“=”等。这样的行是“表格格式化功能”自动添加的分割表标题与表头行的装饰行。
        /// </summary>
        /// <param name="lineText">文本行</param>
        private bool IsTableCaptionSplittor(string lineText)
        {
            if (string.IsNullOrEmpty(lineText)) return false;

            foreach (char c in lineText)
            {
                if (c == '=' || c == '｜' || c == '|' || c == '＝' ||
                    c == ' ' ||                      //下面这三个是考虑到在<code>块中的文字表也需要格式化。
                    c == '　' ||
                    c == '\t')
                    continue;

                return false;
            }

            return true;
        }

        /// <summary>
        /// 给二维文字表插入列。
        /// </summary>
        internal void InsertTableColumn()
        {
            var selEndOffset = this.SelectionStart + this.SelectionLength;
            var curLine = this.Document.GetLineByOffset(selEndOffset);
            var curLineLeftText = this.Document.GetText(curLine.Offset, selEndOffset - curLine.Offset);

            int pipesCount = 0;
            foreach (char c in curLineLeftText)
            {
                if (c == '|' || c == '｜')
                {
                    pipesCount++;
                }
            }

            int? insertColumnIndex = null;
            if (curLineLeftText.StartsWith("|") || curLineLeftText.StartsWith("｜"))
            {
                insertColumnIndex = pipesCount;
            }
            else
            {
                insertColumnIndex = pipesCount + 1;
            }

            Format2DTextTable(insertColumnIndex.Value - 1);

            JumpToNextCell();
            if (Globals.MainWindow.miSelectCellFirst.IsChecked)
            {
                JumpToNextCell();//再调用一次，选中新插入的列在当前行的单元格。
            }
        }

        #region 便于 IronPython 脚本调用的方法

        public 选择区 选择区
        {
            get { return new 选择区(SelectionStart, 选择的文本, this); }
        }

        /// <summary>
        /// SelectedText 的别名。
        /// </summary>
        public string seld { get { return this.SelectedText; } set { this.SelectedText = value; } }
        public string seld_txt { get { return this.SelectedText; } set { this.SelectedText = value; } }
        public string 选择的文本 { get { return this.SelectedText; } set { this.SelectedText = value; } }

        /// <summary>
        /// 活动文本文本，包括从 选择区头部所在行的头部 到 选择区尾部所在行 的尾部间的所有文本。
        /// 它一定是包含 SelectedText 的。
        /// </summary>
        public string actived_text
        {
            get
            {
                return this.Document.GetText(fst_doc_ln.Offset, lst_doc_ln.EndOffset - fst_doc_ln.Offset);
            }
        }
        public string actd_txt => actived_text;
        public string ActivedText => actived_text;
        public string 活动文本 { get { return actived_text; } }

        /// <summary>
        /// 取选择区到行首的文本
        /// </summary>
        public string LeftText
        {
            get
            {
                var selOffset = this.SelectionStart;
                var fstLine = this.Document.GetLineByOffset(selOffset);
                if (fstLine == null) return null;
                return this.Document.GetText(fstLine.Offset, selOffset - fstLine.Offset);
            }
        }
        /// <summary>
        /// LeftText 的别名。
        /// </summary>
        public string lt { get { return LeftText; } }
        public string l_txt { get { return LeftText; } }
        public string lt_txt { get { return LeftText; } }
        public string lft_txt { get { return LeftText; } }
        public string left_txt { get { return LeftText; } }
        public string left_text { get { return LeftText; } }
        public string 选择区左侧文本 { get { return LeftText; } }

        /// <summary>
        /// 取选择区尾到行尾的文本。
        /// </summary>
        public string RightText
        {
            get
            {
                var selEndOffset = this.SelectionStart + this.SelectionLength;
                var lastLine = this.Document.GetLineByOffset(selEndOffset);
                if (lastLine == null) return null;
                return this.Document.GetText(selEndOffset, lastLine.EndOffset - selEndOffset);
            }
        }
        /// <summary>
        /// RightText 的别名。
        /// </summary>
        public string rt { get { return RightText; } }
        public string r_txt { get { return RightText; } }
        public string rt_txt { get { return RightText; } }
        public string rgt_txt { get { return RightText; } }
        public string right_txt { get { return RightText; } }
        public string right_text { get { return RightText; } }
        public string 选择区右侧文本 { get { return RightText; } }

        /// <summary>
        /// 选择头。指选择区头到它所在行的行尾的文本。
        /// </summary>
        public string SelectedHead
        {
            get
            {
                var selOffset = this.SelectionStart;
                var fstLine = this.Document.GetLineByNumber(selOffset);
                if (fstLine == null) return null;
                return this.Document.GetText(selOffset, fstLine.EndOffset);
            }
        }
        /// <summary>
        /// SelectedHead 的别名。
        /// </summary>
        public string sdh { get { return SelectedHead; } }
        public string seld_head { get { return SelectedHead; } }
        public string selected_head { get { return SelectedHead; } }
        public string ht { get { return SelectedHead; } }
        public string hd_txt { get { return SelectedHead; } }
        public string head_txt { get { return SelectedHead; } }
        public string head_text { get { return SelectedHead; } }
        public string 选择区头到行尾 { get { return SelectedHead; } }

        /// <summary>
        /// 选定文本尾部到行首的文本。
        /// </summary>
        public string SelectedTail
        {
            get
            {
                var selEndOffset = this.SelectionStart + this.SelectionLength;
                var lstLine = this.Document.GetLineByOffset(selEndOffset);
                if (lstLine == null) return null;
                return this.Document.GetText(lstLine.Offset, selEndOffset - lstLine.Offset);
            }
        }
        /// <summary>
        /// SelectedTail 的别名。
        /// </summary>
        public string sdt { get { return SelectedTail; } }
        public string seld_tail { get { return SelectedTail; } }
        public string selected_tail { get { return SelectedTail; } }
        public string tt { get { return SelectedTail; } }
        public string tail_txt { get { return SelectedTail; } }
        public string tail_text { get { return SelectedTail; } }
        public string 选择区尾到行首 { get { return SelectedTail; } }

        /// <summary>
        /// 选择区第一行
        /// </summary>
        public DocumentLine first_document_line { get { return this.Document.GetLineByOffset(this.SelectionStart); } }
        /// <summary>
        /// first_line 别名。
        /// </summary>
        public DocumentLine fst_doc_ln { get { return first_document_line; } }
        /// <summary>
        /// 选择区最后一行。
        /// </summary>
        public DocumentLine last_document_line { get { return this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength); } }
        /// <summary>
        /// last_line 别名。
        /// </summary>
        public DocumentLine lst_doc_ln { get { return last_document_line; } }
        /// <summary>
        /// 选择区第一行文本。
        /// </summary>
        public string first_line
        {
            get
            {
                if (first_document_line == null || first_document_line.Offset < 0 || first_document_line.EndOffset >= this.Text.Length) return null;
                return this.Document.GetText(first_document_line.Offset, first_document_line.Length);
            }
        }
        /// <summary>
        /// first_line 别名。
        /// </summary>
        public string fst_ln { get { return first_line; } }
        public string 选择区头部所在行文本 { get { return first_line; } }

        /// <summary>
        /// 选择区最后一行文本。
        /// </summary>
        public string last_line
        {
            get
            {
                if (last_document_line == null || last_document_line.Offset < 0 || last_document_line.EndOffset >= this.Text.Length) return null;
                return this.Document.GetText(last_document_line.Offset, last_document_line.Length);
            }
        }
        /// <summary>
        /// last_line_text 别名。
        /// </summary>
        public string lst_ln { get { return last_line; } }
        public string 选择区尾部所在行文本 { get { return last_line; } }

        /// <summary>
        /// 选择区前一行。PreviewLine() 方法的别名。
        /// </summary>
        public DocumentLine preview_document_line
        {
            get
            {
                try
                {
                    var fst_line = this.Document.GetLineByOffset(this.SelectionStart);
                    return this.Document.GetLineByNumber(fst_line.LineNumber - 1);
                }
                catch { return null; }
            }
        }

        public DocumentLine pre_doc_ln => preview_document_line;

        /// <summary>
        /// 选择区前一行的文本。
        /// </summary>
        public string preview_line
        {
            get
            {
                if (preview_document_line == null) return null;
                return this.Document.GetText(preview_document_line.Offset, preview_document_line.Length);
            }
        }
        /// <summary>
        /// preview_line_text 别名。
        /// </summary>
        public string pre_ln { get { return preview_line; } }
        public string pl => preview_line;
        public string p_l => preview_line;
        public string 选择区头部前一行文本 { get { return preview_line; } }

        /// <summary>
        /// 选择区后一行。 NextLine() 方法的别名。
        /// </summary>
        public DocumentLine next_document_line
        {
            get
            {
                try
                {
                    var lst_line = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
                    return this.Document.GetLineByNumber(lst_line.LineNumber + 1);
                }
                catch { return null; }
            }
        }
        /// <summary>
        /// next_line 别名。
        /// </summary>
        public DocumentLine nxt_doc_ln { get { return next_document_line; } }
        /// <summary>
        /// 选择区后一行的文本。
        /// </summary>
        public string next_line
        {
            get
            {
                if (next_document_line == null) return null;
                return this.Document.GetText(next_document_line.Offset, next_document_line.Length);
            }
        }
        /// <summary>
        /// next_line 别名。
        /// </summary>
        public string nxt_ln { get { return next_line; } }
        public string nl => next_line;
        public string n_l => next_line;
        public string 选择区尾部后一行文本 { get { return next_line; } }

        /// <summary>
        /// 选择文本。是 Select() 方法的别名。
        /// </summary>
        public void sel(int start, int length)
        {
            this.Select(start, length);
        }

        public void 选择文本(int start, int length)
        {
            this.Select(start, length);
        }

        public void 置选择区(int start, int length)
        {
            this.Select(start, length);
        }

        /// <summary>
        /// 在现有选择区起始位置上偏移，方便输入文本。
        /// </summary>
        /// <param name="append_offset">偏移量。</param>
        /// <param name="byHead">是否按选择区头部偏移。默认为 true，按头部（false 按尾部）。</param>
        public void 移动插入点(int append_offset, bool byHead = true)
        {
            int newOffset;
            if (byHead)
            {
                var sel_start = this.SelectionStart;
                newOffset = sel_start + append_offset;

                if (newOffset < 0)
                {
                    newOffset = 0;
                }
                else if (newOffset >= this.Text.Length)
                {
                    newOffset = this.Text.Length - 1;
                }
            }
            else
            {
                var sel_end = this.SelectionEnd;
                newOffset = sel_end + append_offset;

                if (newOffset < 0)
                {
                    newOffset = 0;
                }
                else if (newOffset >= this.Text.Length)
                {
                    newOffset = this.Text.Length - 1;
                }
            }
            this.Select(newOffset, 0);
        }

        public int 选择区头
        {
            get { return this.SelectionStart; }
        }

        public int 选择区尾
        {
            get { return this.SelectionEnd; }
        }

        /// <summary>
        /// 选取选择区起始位置到起始位置所在行行首之间的文本。
        /// </summary>
        public void SelectLeft()
        {
            var fstline = fst_doc_ln;
            this.Select(fstline.Offset, SelectionStart - fstline.Offset);
        }
        /// <summary>
        /// 选取选择区起始位置到起始位置所在行行首之间的文本。
        /// </summary>
        public void sel_left() => SelectLeft();
        public void sel_lft() => SelectLeft();
        public void 选择区头选到行首() => SelectLeft();

        /// <summary>
        /// 选取选择区开始位置到结束位置所在行行尾之间的文本。
        /// </summary>
        public void SelectRight()
        {
            var lstline = lst_doc_ln;
            this.Select(SelectionStart, lstline.EndOffset - SelectionStart);
        }
        /// <summary>
        /// 选取选择区开始位置到结束位置所在行行尾之间的文本。
        /// </summary>
        public void sel_right() => SelectRight();
        public void sel_rgt() => SelectRight();
        public void 选择区尾选到行尾() => SelectRight();

        /// <summary>
        /// 选取选择区头文本。
        /// </summary>
        public void SelectHead()
        {
            var fstline = fst_doc_ln;
            this.Select(SelectionStart, fstline.EndOffset - SelectionStart);
        }
        public void sel_head() => SelectHead();
        public void 选择区头选到行尾() => SelectHead();

        /// <summary>
        /// 选取选择区尾文本。
        /// </summary>
        public void SelectTail()
        {
            var lstline = lst_doc_ln;
            this.Select(lstline.Offset, SelectionStart + SelectionLength - lstline.Offset);
        }
        public void sel_tail() => SelectTail();
        public void 选择区尾选到行头() => SelectTail();

        /// <summary>
        /// 选择区头部索引。
        /// </summary>
        public int sel_start { get { return SelectionStart; } }
        public int 选择区头部索引 { get { return SelectionStart; } }

        /// <summary>
        /// 选择区尾部索引。
        /// </summary>
        public int SelectionEnd { get { return SelectionStart + SelectionLength; } }
        /// <summary>
        /// 选择区尾部索引别名。
        /// </summary>
        public int sel_end { get { return SelectionEnd; } }
        public int 选择区尾部索引 { get { return SelectionEnd; } }

        /// <summary>
        /// 选择区长度。
        /// </summary>
        public int sel_len { get { return SelectionLength; } }
        public int 选择区长度 { get { return SelectionLength; } }

        /// <summary>
        /// 选中前一行文本。
        /// </summary>
        /// <returns>如果不存在前一行，返回 false。</returns>
        public bool SelectPreviewLine()
        {
            try
            {
                if (preview_document_line == null) return false;
                this.Select(preview_document_line.Offset, preview_document_line.Length);
                return true;
            }
            catch { return false; }
        }
        public bool sel_pre_ln() => SelectPreviewLine();
        public bool 选择前一行文本() => SelectPreviewLine();

        /// <summary>
        /// 选中后一行文本。
        /// </summary>
        /// <returns>如果不存在后一行，返回 false。</returns>
        public bool SelectNextLine()
        {
            try
            {
                if (next_document_line == null) return false;
                this.Select(next_document_line.Offset, next_document_line.Length);
                return true;
            }
            catch { return false; }
        }
        public bool sel_nxt_ln() => SelectNextLine();
        public bool 选择后一行文本() => SelectNextLine();

        /// <summary>
        /// 选择区涉及到的所有行。
        /// </summary>
        public List<DocumentLine> selected_document_lines
        {
            get
            {
                try
                {
                    var list = new List<DocumentLine>();
                    for (int i = fst_doc_ln.LineNumber; i <= lst_doc_ln.LineNumber; i++)
                    {
                        list.Add(this.Document.GetLineByNumber(i));
                    }
                    return list;
                }
                catch { return null; }
            }
        }
        public List<DocumentLine> seld_doc_lns => selected_document_lines;
        /// <summary>
        /// 活动文本所涉及到的所有行——这种情况下它和 selected_document_line 的结果是相同的。
        /// 因为活动文本（active_text）是指从选择区头部延伸到所在行的头部，并从选择区尾部延伸到所在行尾部。
        /// </summary>
        public List<DocumentLine> actived_document_lines => selected_document_lines;
        public List<DocumentLine> actd_doc_lns => selected_document_lines;

        /// <summary>
        /// 选中的文本。
        /// </summary>
        /// <returns></returns>
        public string[] selected_lines
        {
            get
            {
                return this.SelectedText.Split(new string[] { "\r\n", }, StringSplitOptions.None);
            }
        }
        public string[] seld_lns => selected_lines;
        public string[] 选择的所有文本行 => selected_lines;

        public string[] actived_lines
        {
            get { return this.actived_text.Split(new string[] { "\r\n", }, StringSplitOptions.None); }
        }
        public string[] actd_lns => actived_lines;
        public string[] 活动的所有文本行 => actived_lines;

        public Tuple<int, int> 取行起始位置与长度(int lineNumber)
        {
            if (lineNumber < 1) lineNumber = 1;
            if (lineNumber > this.Document.LineCount) lineNumber = this.Document.LineCount;

            var dLine = this.Document.GetLineByNumber(lineNumber);
            return new Tuple<int, int>(dLine.Offset, dLine.Length);
        }

        public string 取文本(IronPython.Runtime.PythonTuple tuple)
        {
            if (tuple == null || tuple.Count != 2)
            {
                throw new Exception("传入的无组必须有且仅有两个整数成员。前者用以指定起始索引，后者用以指定要取的文本的长度。");
            }
            return this.Document.GetText((int)tuple[0], (int)tuple[1]);
        }

        public void 开始更改()
        {
            this.BeginChange();
        }

        public void 开始修改()
        {
            this.BeginChange();
        }

        public void 结束更改()
        {
            this.EndChange();
        }

        public void 结束修改()
        {
            this.EndChange();
        }


        public void 置只读属性(bool isReadonly = true)
        {
            this.IsReadOnly = isReadonly;
        }

        public List<QuestionInfo> 取选择题列表(bool choiceItemsConfounded = true)
        {
            var list = QuestionBuilder.取选择题列表(this.Text, false, choiceItemsConfounded);

            foreach (var q in list)
            {
                q.MarkdownImageLinkRealPath = this.MasterEditor.GetRealPathFromRelativeImagePath(q.MarkdownImagePath);
            }

            return list;
        }

        public void 更新选择题列表(List<QuestionInfo> qilst)
        {
            this.BeginChange();

            var trimChars = new char[] { ' ', '　', '\t', '\r', '\n', };
            for (int i = qilst.Count - 1; i >= 0; i--)
            {
                var qi = qilst[i];
                if (qi.ShouldRemove)
                {
                    this.Document.Replace(qi.Match.Index, qi.Match.Length, "");
                    continue;
                }
                StringBuilder sb = new StringBuilder();
                sb.Append("　　试题＞＞(." + (i + 1).ToString() + ".)");
                sb.Append(qi.TitleString.Trim(new char[] { '\r', '\n', ' ', '　', '\t', }));
                sb.Append("\r\n\r\n");

                if (string.IsNullOrWhiteSpace(qi.MarkdownLinkText) == false)
                {
                    sb.Append(qi.MarkdownLinkText.Trim(trimChars));
                    sb.Append("\r\n\r\n");
                }

                foreach (var ci in qi.ChoiceItems)
                {
                    if (ci.IsAnswer)
                    {
                        sb.Append("　　答案＞＞");
                        sb.Append(ci.Text.Replace("\r", "").Replace("\n", "").Trim());
                        sb.Append("\r\n");
                        sb.Append("　　解析＞＞");
                        sb.Append(ci.AnalysisText.Replace("\r", "").Replace("\n", "").Trim());
                        sb.Append("\r\n\r\n");
                        var checkPoints = ci.CheckPoints.Replace("\r", "").Replace("\n", "").Trim();
                        if (string.IsNullOrWhiteSpace(checkPoints) == false)
                        {
                            sb.Append("　考查点＞＞");
                            sb.Append(checkPoints);
                            sb.Append("\r\n\r\n");
                        }
                    }
                }

                foreach (var ci in qi.ChoiceItems)
                {
                    if (ci.IsAnswer == false)
                    {
                        sb.Append("　　错项＞＞");
                        sb.Append(ci.Text.Replace("\r", "").Replace("\n", "").Trim());
                        sb.Append("\r\n");
                        sb.Append("　　解析＞＞");
                        sb.Append(ci.AnalysisText.Replace("\r", "").Replace("\n", "").Trim());
                        sb.Append("\r\n\r\n");
                        var checkPoints = ci.CheckPoints.Replace("\r", "").Replace("\n", "").Trim();
                        if (string.IsNullOrWhiteSpace(checkPoints) == false)
                        {
                            sb.Append("　考查点＞＞");
                            sb.Append(checkPoints);
                            sb.Append("\r\n\r\n");
                        }
                    }
                }

                sb.Append("〓〓〓〓〓〓");
                this.Document.Replace(qi.Match.Index, qi.Match.Length, sb.ToString());
            }
            this.EndChange();
        }

        public List<Question> 取试题列表(bool choiceItemsConfounded = false)
        {
            return this.MasterEditor?.取试题列表(choiceItemsConfounded);
        }

        #endregion 便于 IronPython 脚本调用的方法

        #region 调用宿主方法

        public string 完整文件路径 { get => 主编辑器?.完整文件路径; }

        public string 文件路径 { get => 主编辑器?.完整文件路径; }

        public string 标题文本_非实时 { get => 主编辑器?.标题文本_非实时; }

        public string 文件短名 { get => 主编辑器?.文件短名; }

        public bool 是否已修改 { get => 主编辑器 == null ? false : 主编辑器.是否已修改; }

        public void 发送到右侧对照区()
        {
            this.主编辑器?.发送到右侧对照区();
        }

        public void 发送到左侧对照区()
        {
            this.主编辑器?.发送到左侧对照区();
        }

        public static bool 是否被加密(string filePath)
        {
            return MarkdownEditor.是否被加密(filePath);
        }

        public void 重做()
        {
            this.主编辑器?.重做();
        }

        public void 撤销()
        {
            this.主编辑器?.撤销();
        }

        public void 粘贴()
        {
            this.主编辑器?.粘贴();
        }

        public void 复制()
        {
            this.主编辑器?.复制();
        }

        public void 剪切()
        {
            this.主编辑器?.剪切();
        }

        public bool 是否对照区编辑器 { get => 主编辑器 == null ? false : 主编辑器.是否对照区编辑器; }

        public bool 封装选择区(string leftWrapText, string rightWrapText)
        {
            return 主编辑器 == null ? false : this.主编辑器.封装选择区(leftWrapText, rightWrapText);
        }

        public string 资源文件夹短名 { get => 主编辑器?.资源文件夹短名; }

        public string 资源文件夹路径 { get => 主编辑器?.资源文件夹路径; }

        public string 图像资源文件夹路径 { get => 主编辑器?.图像资源文件夹路径; }

        public string 声音资源文件夹路径 { get => 主编辑器?.声音资源文件夹路径; }

        public string 视频资源文件夹路径 { get => 主编辑器?.视频资源文件夹路径; }

        public string 编译并预览(bool callSystemDefaultWebBrowser = false)
        {
            return this.主编辑器?.编译并预览(callSystemDefaultWebBrowser);
        }

        public string 保存()
        {
            return this.主编辑器?.保存();
        }

        public string 在工作区中的相对路径 { get => 主编辑器?.在工作区中的相对路径; }


        public void 粘贴成代码()
        {
            this.主编辑器?.粘贴成代码();
        }

        public Dictionary<string, string> 递归取配置词典(string header, string footer, bool byRegex = false, int startIndex = 0, bool ignoreCase = true, bool multiLine = true)
        {
            return this.主编辑器?.递归取配置词典(header, footer, byRegex, startIndex, ignoreCase, multiLine);
        }

        public Dictionary<string, string> 递归取配置字典(string header, string footer, bool byRegex = false, int startIndex = 0, bool ignoreCase = true, bool multiLine = true)
        {
            return this.主编辑器?.递归取配置字典(header, footer, byRegex, startIndex, ignoreCase, multiLine);
        }

        public void 插到行首(int lineNumber, string txt)
        {
            this.主编辑器?.插到行首(lineNumber, txt);
        }

        public void 插到行尾(int lineNumber, string txt)
        {
            this.主编辑器?.插到行尾(lineNumber, txt);
        }

        public void ExportQuestionsToPPTX(string saveFilePath = null, string testpaper_title = null)
        {
            this.主编辑器?.ExportQuestionsToPPTX(saveFilePath, testpaper_title);
        }
        #endregion
    }

    /// <summary>
    /// 此类用以描述编辑器的选择区。
    /// </summary>
    public class 选择区
    {
        public 选择区(int offset, string text, MarkdownEditorBase editor)
        {
            起点 = offset;
            文本 = text;
            this.editor = editor;
        }

        private MarkdownEditorBase editor;

        public int 起点 { get; }

        public int 终点
        {
            get { return 起点 + (文本 == null ? 0 : 文本.Length); }
        }

        public int 长度
        {
            get { return 终点 - 起点; }
        }

        public string 文本 { get; }

        /// <summary>
        /// 此方法以指定文本替换指定区域中 **所有文本**。
        /// </summary>
        /// <param name="replacement"></param>
        /// <param name="byRegex"></param>
        public void 替换(string replacement = null)
        {
            if (editor == null) return;
            if (replacement == null) replacement = "";

            editor.Replace(起点, 长度, replacement);
        }
    }

    public class TableCellArray
    {
        public string[] Cells { get; set; }

        public TableLineType Type { get; set; }
    }

    /// <summary>
    /// 临时使用的文字命令。例如：空格键换行等。
    /// </summary>
    public class TextCommand
    {
        /// <summary>
        /// 命令名称。
        /// </summary>
        public string CommandName { get; set; }
        /// <summary>
        /// 命令参数。
        /// </summary>
        public string Parameters { get; set; }
    }

    public enum TabKeyMode
    {
        /// <summary>
        /// 无任何特殊效果，只是输入 Tab 字符。
        /// </summary>
        Default,
        /// <summary>
        /// 当位于右标签左侧时，跳过右标签。（例如跳过当前插入点位置后面紧跟的一个右实心方括号。）
        /// </summary>
        JumpOverTag,
        /// <summary>
        /// 在标签对之间跳转。（例如从一对双引号中间跳转到另一对双引号中间。）
        /// </summary>
        JumpBetweenTags,
        /// <summary>
        /// 执行自定义 IronPython 脚本。
        /// </summary>
        RunScript,
    }
}
